Server Control with dropdown and button, and trigger own event - asp.net

i have a C# server control which contains one button and one dropdown
i want to response.write the dropdown selectedindex to the page while the button click
and i dont want to make the event handler on page level (aspx), but i want to code it inside the control and compile as dll
my flow is :
construct the button and dropdown, which the control class is load
override CreateChildControls to add the button into the server control and bind the click event to it
override the RenderControl to add the dropdown to a table, and then render the button
finally i found that the button event can be click, but it just cant get the dropdown selected item, when i select the second one
here is the code :
public class ServerControl1 : WebControl, INamingContainer
{
public ServerControl1()
{
_oBtn = new Button();
_oBtn.ID = "btn";
_oBtn.Text = "Click Me";
_oBtn.Click += new EventHandler(_oBtn_Click);
_ddl = new DropDownList();
_ddl.ID = "ddl";
_ddl.Items.add(new ListItem("xxxxxxxx", "xxxxxxxx"))
_ddl.Items.add(new ListItem("yyyyyyy", "yyyyyyy"))
}
protected override void CreateChildControls()
{
this.Controls.Add(_oBtn);
base.CreateChildControls();
}
public override void RenderControl(HtmlTextWriter writer)
{
AddAttributesToRender(writer);
Table m_oTable = new Table();
TableRow m_oRow;
TableCell m_oCell;
m_oCell = new TableCell();
m_oCell.Controls.Add(_ddl);
m_oRow.Cells.Add(m_oCell);
m_oTable.Rows.Add(m_oRow);
m_oTable.RenderControl(writer);
_oBtn.RenderControl(writer);
}
protected void _oBtn_Click(object sender, EventArgs e)
{
if (_ddl.SelectedIndex != 0)
{
Page.Response.Redirect(Url + "&f0=" + _ddl.SelectedIndex);
}
else
{
Page.Response.Write("nonononon");
}
}
}

In order to preserve the state (ViewState) of the dynamically added controls (button, dropdownlist), you have to make sure they are added to the Control Tree hierarchy.
-> Page
-> WebControl
-> Button
-> DropdownList
The proper way to initialize the Child controls in a WebControl is in the Init event.
/// <summary>
/// Initialization of controls
/// </summary>
/// <param name="e"></param>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
_oBtn = new Button();
_ddl = new DropDownList();
m_oTable = new Table();
m_oRow = new TableRow();
m_oCell = new TableCell();
_oBtn.ID = "btn";
_oBtn.Text = "Click Me";
_oBtn.Click += new EventHandler(_oBtn_Click);
_ddl.ID = "ddl";
_ddl.Items.Add(new ListItem("xxxxxxxx", "xxxxxxxx"));
_ddl.Items.Add(new ListItem("yyyyyyy", "yyyyyyy"));
_ddl.EnableViewState = true;
_ddl.AutoPostBack = false;
}
If IsPostaback than before the Load event of the control, the state of the Child controls is restored from the ViewState (ex: current button text and selected index are set).
Next step is to add this child controls in the Control Tree hierarchy in the CreateChildControls method
protected override void CreateChildControls()
{
m_oCell.Controls.Add(_ddl);
m_oRow.Cells.Add(m_oCell);
m_oTable.Rows.Add(m_oRow);
this.Controls.Add(_oBtn);
this.Controls.Add(m_oTable);
base.CreateChildControls();
}
and to render the control. You have to avoid initializing or adding controls at this point on:
public override void RenderControl(HtmlTextWriter writer)
{
m_oTable.RenderControl(writer);
_oBtn.RenderControl(writer);
_txt.RenderControl(writer);
}

Related

Creating dynamic controls in Page_PreRender based on viewstate causes button OnClick event to not work

I realize that dynamic controls should be created within Page_Load and Page_Init in order for them to be registered in the control tree.
I have created a custom control that requires the use of ViewState in a button OnClick event. This ViewState is then used to dynamically create controls.
Since the life-cycle will go: Page Load -> Button Click -> Page PreRender. The view-state will not be updated until "Button Click", thus I am creating my dynamic controls in Page PreRender. However, creating a button and programatically assigning the OnClick EventHandler in Page_PreRender does not work.
Does anyone know how I can get this to work?
btn_DeleteTableRow_Click will not fire. This is setup in CreatePartRows()
Here is my example:
<asp:UpdatePanel ID="up_RMAPart" runat="server" UpdateMode="Conditional" EnableViewState="true" ChildrenAsTriggers="true">
<ContentTemplate>
<div class="button" style="width: 54px; margin: 0px; float: right;">
<asp:Button ID="btn_AddPart" runat="server" Text="Add" OnClick="btn_AddPart_Click" />
</div>
<asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
</asp:Table>
<div class="clear"></div>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn_AddPart" EventName="Click" />
</Triggers>
Code Behind:
[Serializable]
public struct Part
{
public string PartName;
public int Quantity;
public int PartID;
public Part(string sPartName, int iQuantity, int iPartID)
{
PartName = sPartName;
Quantity = iQuantity;
PartID = iPartID;
}
}
public partial class RMAPart : System.Web.UI.UserControl
{
private Dictionary<string,Part> m_RMAParts;
private int m_RowNumber = 0;
public Dictionary<string, Part> RMAParts
{
get
{
if (ViewState["m_RMAParts"] != null)
return (Dictionary<string, Part>)ViewState["m_RMAParts"];
else
return null;
}
set
{
ViewState["m_RMAParts"] = value;
}
}
public int RowNumber
{
get
{
if (ViewState["m_RowNumber"] != null)
return Convert.ToInt32(ViewState["m_RowNumber"]);
else
return 0;
}
set
{
ViewState["m_RowNumber"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
RMAParts = new Dictionary<string, Part>();
RowNumber = 0;
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber = 1;
CreatePartRows();
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
CreatePartRows();
}
private void CreatePartRows()
{
Table_Parts.Controls.Clear();
TableHeaderRow thr = new TableHeaderRow();
TableHeaderCell thc1 = new TableHeaderCell();
thc1.Controls.Add(new LiteralControl("Part"));
thr.Cells.Add(thc1);
TableHeaderCell thc2 = new TableHeaderCell();
thc2.Controls.Add(new LiteralControl("Quantity"));
thr.Cells.Add(thc2);
TableHeaderCell thc3 = new TableHeaderCell();
thc3.Controls.Add(new LiteralControl(""));
thr.Cells.Add(thc3);
Table_Parts.Rows.Add(thr);
foreach (KeyValuePair<string, Part> kvp in RMAParts)
{
string[] sKey = kvp.Key.Split('_');
TableRow tr = new TableRow();
tr.ID = kvp.Key;
TableCell tc1 = new TableCell();
TextBox tb_Part = new TextBox();
tb_Part.ID = "tb_Part_" + sKey[1];
tb_Part.CssClass = "textbox1";
tc1.Controls.Add(tb_Part);
tr.Cells.Add(tc1);
TableCell tc2 = new TableCell();
TextBox tb_Quantity = new TextBox();
tb_Quantity.ID = "tb_Quanitty_" + sKey[1];
tb_Quantity.CssClass = "textbox1";
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(tc2);
TableCell tc3 = new TableCell();
Button btn_Delete = new Button();
btn_Delete.ID = "btn_Delete_" + sKey[1];
btn_Delete.CommandArgument = tr.ID;
btn_Delete.Click += new EventHandler(btn_DeleteTableRow_Click);
btn_Delete.Text = "Remove";
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(tc3);
Table_Parts.Rows.Add(tr);
}
}
public void Reset()
{
Table_Parts.Controls.Clear();
RMAParts.Clear();
RowNumber = 0;
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber = 1;
CreatePartRows();
}
protected void btn_AddPart_Click(object sender, EventArgs e)
{
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber++;
}
protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(tr);
RMAParts.Remove(btn.CommandArgument);
}
}
To ensure that the values of input fields persist across postbacks and that server events are raised:
Use view state to keep track of dynamically created controls.
Re-create the controls with the same IDs in LoadViewState (not Load or PreRender, because then the values of input fields will be lost).
The rest of this answer details how I modified your code to get it to work.
RMAPart.ascx
Just for convenience, you can declare the header row in the .ascx:
<asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
<asp:TableRow>
<asp:TableHeaderCell Text="Part" />
<asp:TableHeaderCell Text="Quantity" />
<asp:TableHeaderCell />
</asp:TableRow>
</asp:Table>
RMAPart.ascx.cs
To keep track of dynamically created rows, maintain a list of row IDs in view state:
public partial class RMAPart : System.Web.UI.UserControl
{
private List<string> RowIDs
{
get { return (List<string>)ViewState["m_RowIDs"]; }
set { ViewState["m_RowIDs"] = value; }
}
In the btn_AddPart_Click handler, generate a new row ID and create the controls for the new row:
protected void btn_AddPart_Click(object sender, EventArgs e)
{
string id = GenerateRowID();
RowIDs.Add(id);
CreatePartRow(id);
}
private string GenerateRowID()
{
int id = (int)ViewState["m_NextRowID"];
ViewState["m_NextRowID"] = id + 1;
return id.ToString();
}
private void CreatePartRow(string id)
{
TableRow tr = new TableRow();
tr.ID = id;
TableCell tc1 = new TableCell();
TextBox tb_Part = new TextBox();
tb_Part.ID = "tb_Part_" + id;
tb_Part.CssClass = "textbox1";
tc1.Controls.Add(tb_Part);
tr.Cells.Add(tc1);
TableCell tc2 = new TableCell();
TextBox tb_Quantity = new TextBox();
tb_Quantity.ID = "tb_Quantity_" + id;
tb_Quantity.CssClass = "textbox1";
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(tc2);
TableCell tc3 = new TableCell();
Button btn_Delete = new Button();
btn_Delete.ID = "btn_Delete_" + id;
btn_Delete.CommandArgument = id;
btn_Delete.Click += btn_DeleteTableRow_Click;
btn_Delete.Text = "Remove";
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(tc3);
Table_Parts.Rows.Add(tr);
}
In the btn_DeleteTableRow_Click handler, delete the clicked row and update view state:
protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(tr);
RowIDs.Remove(btn.CommandArgument);
}
Hook Page_Load and start things off by creating the first row:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Reset();
}
}
public void Reset()
{
while (Table_Parts.Rows.Count > 1)
Table_Parts.Rows.RemoveAt(Table_Parts.Rows.Count - 1);
ViewState["m_NextRowID"] = 0;
string id = GenerateRowID();
RowIDs = new List<string> { id };
CreatePartRow(id);
}
Override LoadViewState and re-create the rows using the IDs stored in view state:
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
foreach (string id in RowIDs)
{
CreatePartRow(id);
}
}
}
Dealing with Parts
The code above doesn't use your Part structure at all. To actually move data between your business objects and the user control, you can add a public method that takes a Part collection and uses it to create rows and populate text boxes, and then add another public method that reads out the values of the text boxes into a Part collection.
The button click isn't being fired because control events are called right after the Load event. Your button isn't in the control hierarchy at the time that the asp.net lifecycle is trying to call your event, so it's being dropped. Remember, it's a round-trip and the control has to exist on postback before the LoadComplete event fires for its event handlers to get called.
Create your dynamic controls in the PreLoad or Load event and you should be OK (you will have access to the full viewstate at that time to make any decisions regarding whether or not you need to dynamically create your delete button for that row).
ASP.net Page Lifecycle Docs: http://msdn.microsoft.com/en-us/library/ms178472(v=vs.100).aspx
I think that Robert has the right answer, but needs to be more clear about WHICH Page.Load he is talking about. There are three page requests here.
Initial Page request, no initial button click yet.
Postback on button click. No processing in Page Load. PreRender call creates the new table rows and the new button and links up the button click event to the new button.
Postback after the client clicks on the new button. You need to re-create the dynamic button in Page Load so that the Click event of the dynamic button doesn't get dropped.
Agree with Robert and Bill.
But to add here, in my opinion only way that you would achieve this is by creating a custom control/web server control (inheriting WebControl class), where you override the CreateChildControls method and RenderContents methods. I think this is what you mean when you said, in one of your comments, you are going to code out a grid-view version.

asp.net - dynamically created dropdown not calling the event handler method in post back

I have a page to do a heirarchical search, it starts with a dropdownlist and based on the value selected in the dropdown it will query the database and show the childs in another dropdown list and this continues as long as it hits the leaf... so I've first dropdown added dynamically and it has the event handler on SelectedIndexChanged, when I change the selected value, it triggers the postback but however not calling event handler method.. Not sure what i'm doing wrong here.. or is it a bug??
Using a session variable to keep track the created controls
private List<DynamicControlProperties> PersistedControls
{
get
{
if (_persistedControls == null)
{
if (Session[PersistedControlsKey] == null)
{
Session[PersistedControlsKey] = new List<DynamicControlProperties>();
}
_persistedControls = Session[PersistedControlsKey] as List<DynamicControlProperties>;
}
return _persistedControls;
}
}
And in Page Init, recreating the dynamically generated controls
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
// regenerate the persisted controls
foreach (var prop in PersistedControls)
{
CreateControl(prop);
}
}
In page load, created the very first dropdown
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
// create the control
CreateControl(....)
// bind the data to the dropdown
}
}
In create control method, just creating a label and a dropdown wrap it inside a and adding it to place holder
private DropDownList CreateControl(DynamicControlProperties dynamiccntrlprop)
{
// create a new HTML row
HtmlGenericControlWithParentID tr = new HtmlGenericControlWithParentID("tr");
HtmlGenericControlWithParentID td1 = new HtmlGenericControlWithParentID("td");
HtmlGenericControlWithParentID td2 = new HtmlGenericControlWithParentID("td");
// make sure we set the id and parentid
tr.ID = string.Format("tr{0}", dynamiccntrlprop.ID);
tr.ParentID = dynamiccntrlprop.ParentID;
tr.EnableViewState = true;
// create a new label for dropdown
Label lbl = new Label() { ID = string.Format("lbl{0}", dynamiccntrlprop.DisplayName), Text = dynamiccntrlprop.DisplayName };
// create a new dropdown list
DropDownList ddl = new DropDownList()
{
ID = string.Format("ddl{0}", dynamiccntrlprop.DisplayName),
// set the postback
AutoPostBack = true,
EnableViewState = true
};
// subscribe for the select index changed event
ddl.SelectedIndexChanged += new EventHandler(ddl_SelectedIndexChanged);
// add the controls to table row
td1.Controls.Add(lbl);
td2.Controls.Add(ddl);
tr.Controls.Add(td1);
tr.Controls.Add(td2);
// add the control to place holder
this.filtersPlaceHolder.Controls.Add(tr);
return ddl;
}
Here is the index changed handler,
protected void ddl_SelectedIndexChanged(object sender, EventArgs e)
{
}
Enabled the viewstate,autopostback blah blah blah... recreated the controls with same id in post back.. tried all the answers in google.. but NO luck.. It does trigger the postback when i changed the index but not calling the event handler method..
Any ideas, please???
Many Thanks,
K
You have to make sure that The CreateControl method is called on each and every page postback. This needs to happen to ensure that the dynamic control's event handler is hooked up after the postback.
protected void Page_Load(object sender, EventArgs e)
{
// you shouldn't wrap the call to CreateControl in this 'if' statement
//if (!Page.IsPostBack)
//{
// create the control
CreateControl(....)
// bind the data to the dropdown
//}
}
once you do this, the selected index changed event will fire.
Maybe It is beacuse new value of the dropdownlist don't be loaded.
protected override void LoadViewState(object savedState)
{
// regenerate the persisted controls
foreach (var prop in PersistedControls)
{
CreateControl(prop);
}
base.LoadViewState(savedState);
}

Make event for a multiple buttons and know which button been clicked

I am writing an web app in asp.net,
In the code behind I have this code:
foreach(UserDetails _UD in m_TeachersDetailsList)
{
Button button = new Button();// a Button control
button.Text = "click";
button.ID = "SelectedTeacher";
TableCell tableCell = new TableCell();// a Cell control
tableCell.Controls.Add(button);
TableRow tableRow = new TableRow();
tableRow.Cells.Add(tableCell); // a table row
TableSearchResult.Rows.Add(tableRow); // a table that had been created in the aspx
}
How can I make an event that when you click on the button you go to a function,
and how can I know which button had been click and Brought me to my function.
thanks.
You do this
int id = 0;
foreach(UserDetails _UD in m_TeachersDetailsList)
{
Button button = new Button();// a Button control
button.Text = "click";
button.ID = "selectedTeacher" + id++;
TableCell tableCell = new TableCell();// a Cell control
tableCell.Controls.Add(button);
TableRow tableRow = new TableRow();
tableRow.Cells.Add(tableCell); // a table row
TableSearchResult.Rows.Add(tableRow); // a table that had been created in the aspx
button.Click += new EventHandler(this.button_Click);
}
And common event handler
protected void button_Click(object sender, EventArgs e)
{
//This way you will get the button clicked
Button button = (Button)sender;
}
Important
You will need to add the controls in OnInit.
Hope this works for you.
ASPX
asp:Button ID="btnTest" runat="server" onclick="btn_Click"
Codebehind
protected void btn_Click(object sender, EventArgs e)
{
Button mybutton = (Button)sender;
Response.Write(mybutton.ID);
}
Just use btn_Click as the onclick for each of your buttons. Then use the "sender" to determine which control sent the request.
Don't set the button's id, let it default. Set the button's CommandArgument property instead:
foreach (UserDetails _UD in m_TeachersDetailsList)
{
Button button = new Button();// a Button control
button.Text = "click";
button.CommandArgument = _UD.UserDetailID.ToString(); // some unique identifier
// this is optional, if you need multiple actions for each UserDetail:
button.CommandName = "SomeAction"; // optional
button.Command += new EventHandler(detailButton_Handler);
// ...etc...
}
Then your handler needs to check the CommandName and CommandArgument values:
public void detailButton_Handler(object sender, EventArgs e)
{
string DetailID = e.CommandArgument.ToString();
switch (e.CommandName.ToString())
{
case "SomeAction":
/// Now you know which Detail they clicked on
break;
case "OtherAction":
break;
}
}

datalist custom paging in asp.net

I am doing custom paging for a datalist.Below method gets the required page numbers.
My problem is the click event is not being fired during debug.
Can anyone tel where the problem is.
private void BindPageNumbers(int TotalRecords)
{
int counter = 0;
for(int i=0;i<TotalRecords;i=i+5)
{
counter=counter+1;
LinkButton lnk = new LinkButton();
lnk.Click += new EventHandler(lbl_click);
lnk.ID = "lnkPage" + (counter).ToString();
lnk.Text = (counter).ToString();
pages.Controls.Add(lnk);
Label spacer = new Label();
spacer.Text = " ";
pages.Controls.Add(spacer);
}
}
void lbl_click(object sender, EventArgs e)
{
LinkButton lnk = sender as LinkButton;
int Currentpage = int.Parse(lnk.Text);
ListDataBinding_paging(2, this.Days, (Currentpage-1)*5, 5);
}
Here "ListDataBinding_paging" is the method from where the datalist is being filled.
You are creating your page link buttons dynamically. So they need to be re-created in every post-back early in the life-cycle. I suspect that BindPageNumbers is getting called after the post event data is processed and hence the click event does not get generated.
I suggest you to invoke BindPageNumbers in page_load for creating your buttons early in the life cycle. You can store the total records count in the view-state. If page_load doesn't help then try LoadViewState override - put the code after call to base implementation - something like
protected override void LoadViewState(Object savedState)
{
base.LoadViewState(savedState);
BindPageNumbers((int)ViewState["TotalRecords"]);
}

LinkButton.Command in user control (ascx) will not call specified method

I have the following code:
public partial class queryTerm : System.Web.UI.UserControl
{
private static readonly List<string> BooleanOperators = new List<string> { ".", "AND", "AND NOT", "OR", "OR NOT" };
protected void BuildBoolPanel()
{
var parensOpen = _labelBoolean.Text;
foreach (var #operator in BooleanOperators)
{
if (parensOpen == #operator)
{
continue;
}
var linkButton = new LinkButton();
linkButton.Text = #operator;
linkButton.CommandArgument = #operator;
linkButton.CommandName = "parensOpen";
linkButton.Command += new CommandEventHandler(linkButton_Command);
_popupMenuParensOpen.Controls.Add(linkButton);
var literalLineBreak = new Literal();
literalLineBreak.Text = "<BR/>";
_popupMenuParensOpen.Controls.Add(literalLineBreak);
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
BuildBoolPanel();
}
void linkButton_Command(object sender, CommandEventArgs e)
{
_labelBoolean.Text = (string)e.CommandArgument;
BuildBoolPanel();
}
}
I have a panel(it's _popupMenuParensOpen) that is shown with the hoverextender whenever the cursor finds itself over a specific label in my user control.
This panel has all the boolean operators and '.' meaning not set.
I programatically add the boolean operators as a label in my panel, and I only add those that don't match what it is currently set to. For instance if my label is set to 'AND', when I hover over it, I display everything but 'AND'.
The problem is these never call linkButton_Command even though I instruct them to.
Weirder yet, if I remove the 'if (!this.IsPostBack) in page load, it will call it.
My control is inside an updatePanel.
The issue is that you're dynamically adding the controls during the Page_Load event. When a postback occurs those controls aren't going to exist and will need to be created every time. This is why when you remove your if (!Page.IsPostBack), and the controls are rebuilt, it works.
Since you're building this as a user control, you might want to look in to overriding the CreateChildControls method.

Resources