I have seen a couple of similar questions but none of the solutions worked for me. I have a table and i dinamically add a couple of buttons to it like the following, and i want a single common listener for all buttons:
Button b; //This is defined as a variable in class, not in a function
...
...
void someFunction( . . . )
{
foreach (DataRow row in table.Rows)
{
try
{
string starthour = row["starthour"].ToString(), endhour = row["endhour"].ToString(), day = row["day"].ToString(), building = row["building"].ToString(), room = row["room"].ToString();
int start = Convert.ToInt32(starthour.Substring(0, starthour.Length - 2));
int end = Convert.ToInt32(endhour.Substring(0, endhour.Length - 2));
int startindex = getHourIndex(start);
int endindex = getHourIndex(end);
int dayindex = getDayIndex(day);
for (int i = startindex; i < endindex; i++)
{
b = new Button();
b.Text = subj + numb + " " + section;
Color clr = getCourseColor(subj + numb + section, courses);
b.BackColor = clr;
b.Enabled = true;
b.Click += new EventHandler(button_Click);
table_filter_instructor_schedule.Rows[i].Cells[dayindex].Controls.Add(b);
}
}
catch (Exception)
{
}
}
}
And here is the event handler:
protected void button_Click(object sender, EventArgs e)
{
Response.Redirect("Default.aspx");
}
but the problem is, ebent handler function is never called. Can anyone help me with this*
Thanks
EDIT: Here is how the page looks, and i want to add listener for those buttons:
If you want dynamic controls to work and also raise events, you need to recreate them early in the page lifecycle (Page_Init, Page_Load at the latest). It is important that you assign the same IDs when you recreate the controls and also wire up the event handlers.
Using dynamically created controls usually adds a lot of complexity and you should check whether there is no other way that is simpler. In your function, your buttons are created based on a data table. So it might be a good approach to use a repeater instead of the dynamically created buttons. This allows to wire up events statically.
For a detailed sample on how to create controls dynamically, see this link. However, it also suggests to favor a static approach if possible:
Existing controls can often provide the functionality you get from
creating controls dynamically. For example, controls such as the
Repeater, DataList, and RadioButtonList controls can dynamically
create rows or other control elements when the page runs.
How to avoid adding controls dynamically in your case
In your specific case (based on your image), I'd propose the following static approach:
Add a Repeater to your page that creates the table header in the header template, the rows in the ItemTemplate and the table footer in the FooterTemplate. Add a button for each day in the ItemTemplate. Wire an event to the buttons.
Create a data class that represents one line in the repeater, e.g. time and the data for each day.
When you retrieve the data, convert it to a list of the data class and bind the repeater to it.
Handle the OnItemDataBound event of the repeater to adjust the visibility of the buttons and set the text.
The following sample shows the main parts (I've added only columns for three days):
Repeater
This Repeater creates a very basic HTML table. Please note the buttons in the table rows and the static registration of an event handler.
<asp:Repeater ID="rptTimeTable" runat="server" OnItemDataBound="rptTimeTable_ItemDataBound">
<HeaderTemplate>
<table>
<thead>
<tr>
<td>Time</td>
<td>Mon</td>
<td>Tue</td>
<td>Wed</td>
</tr>
</thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("Time", "{0:t}") %></td>
<td>
<asp:Button ID="btnMon" runat="server" OnClick="btn_ClickHandler" />
</td>
<td>
<asp:Button ID="btnTue" runat="server" OnClick="btn_ClickHandler" />
</td>
<td>
<asp:Button ID="btnWed" runat="server" OnClick="btn_ClickHandler" />
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
Data Class
The data class stores a text for each button that I put on the button later on.
public class RepeaterData
{
public DateTime Time { get; set; }
public string MonText { get; set; }
public string TueText { get; set; }
public string WedText { get; set; }
}
Data binding
I've placed this in Page_Load (only if it is not a PostBack), but you can run this whenever you like.
var data = new List<RepeaterData>();
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(9), MonText = "123", TueText = null, WedText = null });
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(10), MonText = null, TueText = "456", WedText = "789" });
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(11), MonText = null, TueText = null, WedText = null });
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(12), MonText = "123", TueText = null, WedText = null });
rptTimeTable.DataSource = data;
rptTimeTable.DataBind();
OnItemDataBound handler
protected void rptTimeTable_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var data = (RepeaterData)e.Item.DataItem;
SetButtonText(e.Item, "btnMon", data.MonText);
SetButtonText(e.Item, "btnTue", data.TueText);
SetButtonText(e.Item, "btnWed", data.WedText);
}
}
private void SetButtonText(RepeaterItem repeaterItem, string btnId, string btnText)
{
var btn = repeaterItem.FindControl(btnId) as Button;
if (btn != null)
{
if (!string.IsNullOrEmpty(btnText))
btn.Text = btnText;
else
btn.Visible = false;
}
}
Button click handler
protected void btn_ClickHandler(object sender, EventArgs e)
{
// Do whatever you like
}
The problem is that you generate dynamic buttons on your asp page, the ids generated have to be the same over the lifetime of the page. If not the button can not be found at serverside on a postback. The cause can be that you build your table multiple times in the request handling.
Related
I'm having some difficulty with my dynamically generated usercontrols retaining their selected values. I have created a usercontrol with several form fields. On the main page I have a button that will place my usercontrol in a placeholder. The user can create as many of these as needed. I also have a button in the control itself that allows the user to delete any given control. This functionality seems to be working fine.
But, weirdness ensues in the following scenario:
Click button to create a usercontrol on page. Fill out the form
fields.
Click button again to create a second UC and fill out form
fields. All is well at this point. Values in UC#1 retained its
values.
Click button to create third UC and all selected values are
wiped out of UC #1 and #2.
If I refill out the fields in all the UC's then click the button to create a 4th UC, then UC#1 and UC#3 retain their values but UC#2 loses it's values.
Any help would be massively appreciated. I'm going bug-eyed trying to figure this one out. This is my first foray into dynamic usercontrols and so far it's kicking my butt. And I still have to figure out how to populate these UC's with values from the db so the user can come back and edit the form, but one thing at a time.
aspx:
<asp:PlaceHolder ID="placeholderOffenseCodes" runat="server"> </asp:PlaceHolder>
<asp:Button ID="btnAddOffense" runat="server" Text="Add an Offense" CausesValidation="false" OnClick="btnAddOffense_Click" />
<!--The text value determines how many items are initially displayed on the page-->
<asp:Literal ID="ltlCount" runat="server" Text="0" Visible="false" />
<asp:Literal ID="ltlRemoved" runat="server" Visible="false" />
aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
AddAndRemoveDynamicOffenseControls();
}
private void AddAndRemoveDynamicOffenseControls()
{
//Determine which control fired the postback event.
Control c = GetPostBackOffenseControl(Page);
if ((c != null))
{
//If the add button was clicked, increase
//the count to let the page know we want
//to display an additional user control
if (c.ID.ToString() == "btnAddOffense")
{
ltlCount.Text = Convert.ToString(Convert.ToInt16(ltlCount.Text) + 1);
}
}
//Be sure everything in the placeholder control is cleared out
placeholderOffenseCodes.Controls.Clear();
int ControlID = 0;
//Re-add controls every time the page loads.
for (int i = 0; i <= (Convert.ToInt16(ltlCount.Text) - 1); i++)
{
IncidentGroupA_Offenses uc = (IncidentGroupA_Offenses)LoadControl("IncidentGroupA_Offenses.ascx");
//If this particular control id has been deleted
//from the page, DO NOT use it again. If we do, it will
//pick up the viewstate data from the old item that
//had this control id, instead of generating
//a completely new control. Instead, increment
//the control ID so we're guaranteed to get a "new"
//control that doesn't have any lingering information in the viewstate.
while (InDeletedOffenseList("offense" + ControlID) == true)
{
ControlID += 1;
}
//Note that if the item has not been deleted from the page,
//we DO want it to use the same control id
//as it used before, so it will automatically maintain
//the viewstate information of the user control
//for us.
uc.ID = "offense" + ControlID;
//Add an event handler to this control to raise
//an event when the delete button is clicked
//on the user control
uc.RemoveOffenseUC += this.HandleRemoveOffenseUserControl;
//Add the user control to the panel
placeholderOffenseCodes.Controls.Add(uc);
//Add Offense number to label on usercontrol
int OffenseNum = i + 1;
uc.OffenseNumber = "Offense " + OffenseNum;
//Increment the control id for the next round through the loop
ControlID += 1;
}
}
protected void btnAddOffense_Click(object sender, EventArgs e)
{
//handled in page_load
}
private bool InDeletedOffenseList(string ControlID)
{
//Determine if the passed in user control ID
//has been stored in the list of controls that
//were previously deleted off the page
string listvalues = ltlRemoved.Text;
string[] stringSeparators = new string[] { "|" };
string[] DeletedList = listvalues.Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i <= DeletedList.GetLength(0) - 1; i++)
{
if (ControlID == DeletedList[i])
{
return true;
}
}
return false;
}
public void HandleRemoveOffenseUserControl(object sender, EventArgs e)
{
//This handles delete event fired from the user control
//Get the user control that fired this event, and remove it
LinkButton linkBtn = sender as LinkButton;
IncidentGroupA_Offenses uc = (IncidentGroupA_Offenses)linkBtn.Parent;
if (uc != null)
{
placeholderOffenseCodes.Controls.Remove(uc);
}
//Keep a pipe delimited list of which user controls were removed. This will increase the
//viewstate size if the user keeps removing dynamic controls, but under normal use
//this is such a small increase in size that it shouldn't be an issue.
ltlRemoved.Text += uc.ID.ToString() + "|";
//Also, now that we've removed a user control decrement the count of total user controls on the page
ltlCount.Text = Convert.ToString(Convert.ToInt16(ltlCount.Text) - 1);
}
public Control GetPostBackOffenseControl(Page page)
{
Control control = null;
string ctrlname = page.Request.Params.Get("__EVENTTARGET");
if ((ctrlname != null) & ctrlname != string.Empty)
{
control = page.FindControl(ctrlname);
}
else
{
foreach (string ctl in page.Request.Form)
{
Control c = page.FindControl(ctl);
if (c is System.Web.UI.WebControls.Button)
{
control = c;
break;
}
}
}
return control;
}
.ascx.cs:
public event EventHandler RemoveOffenseUC;
protected void btnRemoveOffense_Click(object sender, EventArgs e)
{
//Raise this event so the parent page can handle it
if (RemoveOffenseUC != null)
{
RemoveOffenseUC(sender, e);
}
}
public string OffenseNumber
{
get { return lblOffenseNumber.Text; }
set { lblOffenseNumber.Text = value; }
}
I always thought you had to add dynamic controls at Page_Init to ensure correct loading of ViewState. Maybe that simple change would fix your problem?
Otherwise, I've had luck in the past with avoiding dynamic controls altogether using a repeater. Instead of adding dynamic controls, which webforms is definitely not very good at, add data to a data structure like a List or ADO DataTable, then bind that to an asp:Repeater with the controls you want in it. No messy fussing around with dynamic controls.
Best of luck!
I have a DataGridView with "Type" and "Value" columns. The user selects a data type, and then enters a value compatible with that type, some types (e.g. "Text") accept any string. Other types (e.g. "Yes/No") are restricted to a list of possible values. For each row, I set the cell in the "Value" column to be either a Textbox for freeform types, or a combobox for list types. The DataGridView is bound to a DataTable.
The problem comes in if the user enters a value for one type, but then switches the row to a different type for which the current value is not allowed. No matter what I try, I cannot clear the Value cell. Then when I assign a combobox to the cell, I get a DataError, because the current value of the cell is incompatible. How can I clear the value before changing the cell from a textbox to a combobox?
public enum DataType
{
Text,
YesNo
}
public class IndexedItem
{
public string Name { get; set; }
public int ID {get; set; }
public string strID
{
get { return ID.ToString(); }
}
//constructors & other methods;
}
public static DataTable ParameterTable;
public List<IndexedItem> YesNoList;
In the form constructor (dgvInputs is the DataGridView):
ParameterTable = new DataTable("ParameterTable");
ParameterTable.Columns.Add("Type", typeof(DataType));
ParameterTable.Columns.Add("Value", typeof(string));
YesNoList = new List<IndexedItem>();
YesNoList.Add(new IndexedItem("Yes", 1));
YesNoList.Add(new IndexedItem("No", 0));
var D = (DataGridViewComboBoxColumn)dgvInputs.Columns[0];
D.ValueMember = "Value";
D.DisplayMember = "Display";
D.DataSource = new DataType[] {
DataType.Text,
DataType.YesNo
}.Select(x => new { Display = x.ToString(), Value = (int)x }).ToList();
BindingSource ParamSource = new BindingSource();
ParamSource.DataSource = ParameterTable;
dgvInputs.AutoGenerateColumns = false;
dgvInputs.DataSource = ParamSource;
dgvInputs.Columns[0].DataPropertyName = "Type";
dgvInputs.Columns[1].DataPropertyName = "Value";
And Events:
private void dgvInputs_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
if (dgvInputs.IsCurrentCellDirty) {
dgvInputs.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
private void dgvInputs_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if (e.RowIndex >= 0 && e.ColumnIndex == 0) {
var cb = (DataGridViewComboBoxCell)dgvInputs[0, e.RowIndex];
if (cb.Value != null && cb.Value != DBNull.Value) {
DataType Type = (DataType)cb.Value;
dgvInputs[1, e.RowIndex].Value = string.Empty;
dgvInputs.CommitEdit(DataGridViewDataErrorContexts.Commit);
switch (Type) {
case DataType.YesNo:
dgvInputs[1, e.RowIndex].Dispose();
var newBox = new DataGridViewComboBoxCell();
newBox.DisplayMember = "Name";
newBox.ValueMember = "strID";
newBox.DataSource = YesNoList;
dgvInputs[1, e.RowIndex] = newBox;
break;
default:
dgvInputs[1, e.RowIndex] = new DataGridViewTextBoxCell();
break;
}
}
}
}
If you have it set to "text" and enter something arbitrary, then switch to "YesNo", it gives an error "System.ArgumentException: DataGridViewComboBoxCell value is not valid.", that will reappear any time the cursor is over the cell. Changing it back to a text row causes the original value to reappear.
I am assuming that the problem is that the value is saved in ParameterTable, but I can't get it to propagate my clearing of the original value to ParameterTable. I've tried null and DBNull.Value instead of string.Empty, but neither one made any difference. I added the "CommitEdit" line in hopes of getting it to make the change, but that made no difference either.
Edit:
As it turns out, the problem was this code that I had in the cell change event:
string Default = dgvInputs[4, e.RowIndex].Value as string;
// code switching out text box and combo box above
try
{
dgvInputs[4, e.RowIndex].Value = Default;
} catch (Exception e2) {
MessageBox.Show(e2.GetType().ToString());
}
The idea had been to preserve the value if possible, and I had the messagebox to show me the specific exception I needed to catch, as I was not sure. But apparently this assignment does not immediately induce the exception. That only occurs later, apparently during some other event I am not handling.
It is obvious in hindsight that I should have included this code in the sample. I have no idea now how I overlooked it. My apologies to everybody I led on a wild goose chase by leaving out the critical information. I do appreciate all of your assistance.
You problem is not with clearing the value but with the YesNoList.
The grid compbobox tries to find the value for the current record and there is no empty neither null value in your YesNoList.
You will even get an error if you try to add a new record and first set the DataType without setting the Value.
You can solve this by either adding an empty item to your YesNoList or by setting a default value to the existing record when switching DataType.
Edit:
I know below does not specifically answer your question as it states, but the example may help you out. Consider having two controllers in one cell.
Original
I am not sure if this will help you or not, but I tried to make a very basic program that you discussed. A dataset is created with 2 entries. The first column is the DataType, the second is of Value. If the DataType Text is chosen, the Value cell turns into a Textbox. If the DataType Yes/No is chosen, it hides the Textbox and shows a DropDownList. The idea is to hide one component when not needed.
Default.aspx.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DataGridViewBounds
{
public partial class _Default : Page
{
public enum DataType
{
Text,
YesNo
}
public class IndexedItem
{
public string Name
{ get; set; }
public int ID
{ get; set; }
public string strID
{ get { return ID.ToString(); } }
}
protected void Page_Load (object sender, EventArgs e)
{
if (!IsPostBack)
{
Bind();
}
for (int i = 0; i < dg.Items.Count; ++i)
{
bool ShowText = ((DropDownList)dg.Items[i].Cells[0].Controls[1]).SelectedValue.Equals("text");
((DropDownList)dg.Items[i].Cells[1].Controls[1]).Visible = !ShowText;
((TextBox)dg.Items[i].Cells[1].Controls[3]).Visible = ShowText;
}
}
private void Bind ()
{
DataTable ParameterTable = new DataTable("ParameterTable");
ParameterTable.Columns.Add("", typeof(string));
ParameterTable.Columns.Add("Type", typeof(DataType));
ParameterTable.Columns.Add("Value", typeof(string));
List<ListItem> YesNoList = new List<ListItem>(); // Should be ListItem, not IndexedItem
YesNoList.Add(new ListItem("Yes", "1"));
YesNoList.Add(new ListItem("No", "0"));
DataRow row = ParameterTable.NewRow();
row["Type"] = DataType.Text;
row["Value"] = "Some text";
DataRow row2 = ParameterTable.NewRow();
ParameterTable.Rows.Add(row);
row2["Type"] = DataType.YesNo;
row2["Value"] = "false";
ParameterTable.Rows.Add(row2);
dg.DataSource = ParameterTable;
dg.DataBind();
dg.ShowHeader = true;
dg.Visible = true;
for (int i = 0; i < dg.Items.Count; ++i)
{ // Showing 2 ways to bind the DropDownList items
((DropDownList)dg.Items[i].Cells[0].Controls[1]).Items.Add(new ListItem("Text", "text"));
((DropDownList)dg.Items[i].Cells[0].Controls[1]).Items.Add(new ListItem("Yes/No", "bool"));
((DropDownList)dg.Items[i].Cells[1].Controls[1]).DataSource = YesNoList;
((DropDownList)dg.Items[i].Cells[1].Controls[1]).DataBind();
}
}
}
}
And the Default.aspx page
<%# Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DataGridViewBounds._Default" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<asp:DataGrid ID="dg" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:TemplateColumn HeaderText="Type">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddListType" AutoPostBack="true"></asp:DropDownList>
<asp:Label id="TypeLabel" runat="server" Visible="false"></asp:Label>
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="Value">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddListValue" AutoPostBack="true" Visible="false"></asp:DropDownList>
<asp:TextBox id="ValueLabel" runat="server" Visible="false"></asp:TextBox>
</ItemTemplate>
</asp:TemplateColumn>
</Columns>
</asp:DataGrid>
</asp:Content>
This is the best I could do at the moment without seeing more of the code, but may you can use it. One suggestion, dgvInputs_CurrentCellDirtyStateChanged appears to commit code. I am assuming this is SQL code. You may want to wait until committing until a final 'Submit' button or 'Accept Changes' button has been pressed so that you don't have to call SQL so much, but also if there is an error that occurs between the start of the first SQL call and the last. If an interruption occurs between the two, you may not necessarily want to commit.
I am working on a page that accepts scanned input into a textbox. Once input is entered into the textbox, a user control that contains additional fields for the input item is dynamically generated via autopostback. The user control also contains a link button that is intended to delete the item (itself). Clicking this button generates a "delete" event that is supposed to bubble up to the parent.
Unfortunately this does not seem to happen - the deletion handling code is never reached. My understanding is that it is because the controls have not been generated yet at page load, so the event handlers have not been created yet. But since I don't know what controls need to be generated at page load (since the user input hasn't been processed yet), I can't move the code to generate the user controls to PageLoad.
How should this be handled? Am I going about this the wrong way? Most of my relevant code is below.
Thanks for any help!
page.aspx:
Enter SKU (tab to enter):
<asp:TextBox ID="EntryTextBox" CssClass="textbox" AutoPostBack="true" OnTextChanged="newItem" runat="server"></asp:TextBox>
<asp:Panel ID="itempanel" runat="server">
</asp:Panel>
page.aspx.cs:
protected void newItem(object sender, EventArgs e)
{
// we need to store item entries in ViewState so they comeback on postback;
// they can't be stored in the controls themselves as the controls will
// disappear
ViewState["skus"] += "\t" + EntryTextBox.Text;
ViewState["descs"] += "\t" + itemLookup(EntryTextBox.Text);
// ...more item descriptors...
updateItemPanel();
}
protected void updateItemPanel()
{
// generate a control for each item entered in ViewState
itempanel.Controls.Clear();
List<string> skus = new List<string>(ViewState["items"].ToString().Substring(1).Split('\t'));
List<string> descs = new List<string>(ViewState["descs"].ToString().Substring(1).Split('\t'));
// ...more item descriptors...
int i = 0;
foreach (string sku in skus)
{
item newitemctrl = (item)Page.LoadControl("~/item.ascx");
newitemctrl.line = (i + 1).ToString();
newitemctrl.sku = skus[i];
newitemctrl.description = descs[i];
// ...more item descriptors...
newitemctrl.deleteLinkClicked += new EventHandler(deleteClicked);
itempanel.Controls.Add(newitemctrl);
i++;
}
}
protected void deleteClicked(object sender, EventArgs e)
{
List<string> skus = new List<string>(ViewState["skus"].ToString().Substring(1).Split('\t'));
List<string> descs = new List<string>(ViewState["descs"].ToString().Substring(1).Split('\t'));
// ...more item descriptors...
item olditemctrl = (item)sender;
skus.RemoveAt(Convert.ToInt32(olditemctrl.number) - 1);
descs.RemoveAt(Convert.ToInt32(olditemctrl.number) - 1);
ViewState["skus"] = skus.ToString();
ViewState["descs"] = descs.ToString();
updateItemPanel();
}
item.ascx:
<asp:LinkButton ID="DeleteLinkButton" runat="server" onclick="DeleteLinkButton_Click">Delete</asp:LinkButton>
item.ascx.cs:
public event EventHandler deleteLinkClicked;
protected void DeleteLinkButton_Click(object sender, EventArgs e)
{
if (this.deleteLinkClicked != null)
{
this.deleteLinkClicked(new object(), new EventArgs());
}
}
You can dispatch a postback event in javascript by adding this as onclick event:
__doPostBack("<%= button.ClientID %>", "");
DoPostBack has two arguments, the first is the ID, the second is the event name.
I used this solution successfully.
You'll find more information here.
Note:
This itself does not fire the event automatically, but you can see what you want to happen in the Load Event of your page.
You can get the arguments like this: : Request.Form["__EVENTTARGET"]
You have full access to the form data, so you can also get the values from the dynamically created controls
I am trying to create a UI that creates rows that can be dynamically populated.
I am at the point where I can add a Panel to my list that contains a DropDownList and has an associated Remove Button.
The Remove Button has an OnClick event bound that allows me to remove that specific panel.
I am having an issue when trying to bind a SelectedIndexChanged EventHandler to my DropDownList it does not.
I suspect this had something to do with how I am recreating my controls after every Postback.
I am not asking for or expecting a straightforward "Add this or Change this in the code." I really want to have a understand where I am going wrong, sorry for asking so much! :s
protected void Page_Load(object sender, EventArgs e)
{
//Showing this for the the poster that asked.
if (!IsPostBack)
{
GetClustersFromDB(user);
BindGrid();
BindState();
}
if (Session["persistControls"] != null)
{
persistControls = (List<Panel>)Session["persistControls"];
int count = 0;
foreach (Panel dynamicControl in persistControls)
{
DropDownList list = new DropDownList();
list.ID = "list" + count;
list.SelectedIndexChanged += new EventHandler(list_SelectedIndexChanged);
list.AutoPostBack = true;
list.Items.Add(new ListItem("", "0"));
list.Items.Add(new ListItem("Title", "1"));
dynamicControl.ID = "panel" + count;
Button btnRemove = new Button();
btnRemove.Click += new EventHandler(btnDelete_Click);
btnRemove.Text = "Remove";
btnRemove.CommandArgument = count.ToString();
myPlaceholder.Controls.Add(dynamicControl);
myPlaceholder.Controls.Add(btnRemove);
count++;
}
}
}
protected void btnAdd_Click(object sender, EventArgs e)
{
try
{
DropDownList list = new DropDownList();
list.SelectedIndexChanged += new EventHandler(list_SelectedIndexChanged);
list.AutoPostBack = true;
list.Items.Add(new ListItem("", "0"));
list.Items.Add(new ListItem("Title", "1"));
Panel panelContainer = new Panel();
panelContainer.ID = "panel" + persistControls.Count;
panelContainer.Controls.Add(list);
Button btnRemove = new Button();
btnRemove.Click += new EventHandler(btnDelete_Click);
btnRemove.Text = "Remove";
btnRemove.CommandArgument = persistControls.Count.ToString();
myPlaceholder.Controls.Add(panelContainer); // Pushes the Panel to the page.
persistControls.Add(panelContainer);// Adds our Panel to the Control list
myPlaceholder.Controls.Add(btnRemove); // Pushes our Button to the page.
Session["persistControls"] = persistControls; // put it in the session
}
catch
{
throw;
}
}
protected void btnDelete_Click(object sender, EventArgs e)
{
try
{
int deleteThisOne = int.Parse(((Button)sender).CommandArgument);
persistControls.Remove(persistControls[deleteThisOne]);
Session["persistControls"] = persistControls;
Response.Redirect(Request.Url.ToString());
}
catch
{
throw;
}
}
protected void list_SelectedIndexChanged(object sender, EventArgs e)
{
throw new NotImplementedException();
}
//aspx File Snippets
<asp:Button ID="btnAdd" runat="server" Text="Add Control" onclick="btnAdd_Click" />
<asp:Button ID="btnClear" runat="server" Text="Reset" onclick="btnClear_Click"/>
<asp:PlaceHolder ID="myPlaceholder" runat="server"></asp:PlaceHolder>
Maintaining controls like this is a real pain. You could try an alternative approach:
Create a (small) class that encapsulates the data you want to display in each Panel.
Maintain a List of these objects in Session.
Use an <asp:Repeater> to display the items by data-binding it to your list.
In the repeater's ItemTemplate, you can write out your <asp:Panel> that can contain a <asp:DropDownList>. Use the ItemCommand property of the drop-down list to bind it to a server-side event handler. Similarly you can put an <asp:Button> in the template and bind the ItemCommand property of this to another server-side event handler.
When you click Add, you'll postback to the server, add to your list, save it in session, re-bind the repeater.
When you click a Remove, you'll postback to the server, remove the item from your list, save it in session, re-bind the repeater.
This way lets ASP.Net handle all the control creation for you. However, if you really want to do all that yourself, then this Infinities Loop blog post is required reading.
I don't think this will work. If you create the dropdownlists in the PageLoad event, the viewstate will never get bound to it, and you will never see the option selected by the user.
You should create the controls OnInit, then when the PostBack occurs the ViewState data will get bound to the control and you will be able to access it.
I'm using the commandArgument property of the LinkButton ( Which is wrapped inside a repeater ) to pass two values -as one string- to a second repeater then I try to split them into two values, So I could use them as parameters in my ADO.NET Code (SqlCommand Parameters)....after testing my queries don't return any results but If I passed fixed values for the parameter or change the source of the parameter (just for test from a textbox or querystring or something) I get my results, so I think the problem is in splitting.
I Conduct some arugment values from the ArgumentCommand property of the LinkButton -which is wrapped inside a repeater:
<ItemTemplate>
<asp:LinkButton id="sort_lnkbtn" Text='<%# Eval("value")%>'
CommandArgument='<%#string.Format("{0}|{1}",Eval("arrange_by_id"),Eval("value"))%>' runat="server">
</asp:LinkButton>
</ItemTemplate>
Then I receive these values and cut them into two pieces of information:
string sortByAndArrangeBy = (e.CommandArgument).ToString();
char[] separator = { '|' };
string[] sortByAndArrangeByArray = sortByAndArrangeBy.Split(separator);
Now the ado.net code uses this values as a
using (SqlConnection cn1 = new SqlConnection(ConfigurationManager.ConnectionStrings["testConnectionString"].ConnectionString))
{
using (SqlCommand cm1 = new SqlCommand("SELECT [name] FROM brands WHERE (name like #SearchString + '%' )", cn1))
{
cm1.Parameters.Add("#SearchString", System.Data.SqlDbType.Char);
cm1.Parameters["#SearchString"].Value = sortByAndArrangeByArray[1];
cn1.Open();
using (SqlDataReader dr1 = cm1.ExecuteReader())
{
List_rpt.DataSource = dr1;
List_rpt.DataBind();
}
}
}
Here is a simple solution:
Wrap your item template for the repeater in a control. The control will have the same markup as your item template without the bindings:
Control Markup:
<div>
<asp:LinkButton ID="LnkBtnSort" runat="server" Text="Sort" OnClick="LnkBtnSort_Clicked"/>
</div>
Control Code:
public class SomeControl
{
public event EventHandler Click;
public string ArrangeById
{
set { ViewState["byid"] = value; }
get { return ViewState["byid"].ToString(); }
}
public string Value
{
set { ViewState["val"] = value; }
get { return ViewState["val"].ToString(); }
}
protected void LnkBtnSort_Clicked(object sender, EventArgs e)
{
if( Click != null )
{
this.Click(this, EventArgs.Empty);
}
}
}
So now in the repeater all you have to do is bind an instance of that control to the Container.DataItem:
<ItemTemplate>
<ctrl:SomeControl
ID="someControl"
runat="server"
OnClick="SomeControl_Clicked"
ArrangeById='<%# Eval("arrange_by_id") %>'
Value='<%# Eval("value") %>' />
</ItemTemplate>
The page/control that has the repeater will have one simple method:
protected void SomeControl_Clicked(object sender, EventArgs e)
{
//Here cast the sender to the type of control you made:
SomeControl ctrl = (SomeControl)sender;
string byId = ctrl.ArrangeById;
string val = ctrl.Value;
}
Note: this code may not be 100% correct but it illustrates the point. The flow is simple - the control binds its public properties to whatever you need to bind. When the link is clicked (inside your control) the control doesn't propagate this event to the page. Instead it fires its own event (Click) thus sending a signal to the page that an event occured. however by doing so, it changes the source of the event to itself instead of the actual link button. The page handles the event and everyone is happy.
This way you don't have to care what the CommandArgument is... If this comes up empty, it means that either your data source is empty... or something else happened in the code.