I'm trying to create a custom server control (WebControl) with a text box.
I add asp.net textbox to the custom control in CreateChildControls override. In OnInit override I add event handler to TextBox.TextChanged.
Everything works, except that TextChanged never fires. I looked at viewstate and it looks like my textbox never saves its Text property in the viewstate. I've tried to set Text in various places, including constructor, but nothing works.
How can I get TextBox dynamically added to WebControl to save it's Text in viewstate and get TextChanged event to fire?
I would greatly appreciate an example of WebControl code behind with TextBox being added dynamically and TextChanged event being fired.
The dynamically created control must be created again in each post back, (the pageInit event is the better option) for the event to be fired.
BTW, if you want the TextChanged event to generate a postback you must also set the AutoPostback of the control to true.
fixed it. dynamic control must be created and added in Init event. It must be assigned an ID without special ASP.NET symbols ('$' or ':' inside custom ID will break things). All properties must be assigned after control is added to the controls tree.
here's a working example for Page codebehind:
private readonly TextBox _textBoxTest = new TextBox();
protected void Page_Init( object sender, EventArgs e )
{
this.form1.Controls.Add( _textBoxTest );
_textBoxTest.Text = "TestBoxTest";
_textBoxTest.ID = "TestBoxTestId";
_textBoxTest.TextChanged += this._textBoxTest_TextChanged;
}
void _textBoxTest_TextChanged( object sender, EventArgs e )
{
_textBoxTest.Text = "Worked";
}
for WebControl place init code in OnInit override
This will help you out. In short, you need to handle the viewstate for your Dynamically added control on your own.
Related
I'm aware this question has been asked many times before but I suspect I have a unique scenario.
I'm loading a Child Control (ASCX) and setting a Property on that Control. This works perfectly fine until postback where the property is null.
Herewith the First Class which loads the ChildControl :
protected override void CreateChildControls()
{
MyUserControl control = (MyUserControl)Page.LoadControl(_ascxPath);
control.MyProperty = base.MyProperty
Controls.Add(control);
}
Then, on my Child Control I've got the following code:
public partial class MyUserControl : UserControl
{
public MyType MyProperty { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
//Exception on next line because Property is null (only on postback)
var somevalue = MyProperty.SubProperty;
Ok. Let me try to explain it.
1. Once page is created, you get full page lifecycle
2. You click on some control to create user control, and you get it
3. Now you are entering value to this control, and getting postback
4. On server side postback is handled, but as you can see viewstate actions appear as soon as page is loaded.
One of main purposes of viewstate is handling control events, to see if they are changed, or save their states or something else.
5. If on the moment, when viewstate is loaded you control is still not constructed, then all it's events and values would be ignored.
Solution either make it static control and just hide it, either create it before viewstate actions started.
You need to add the control and set properties in the Page_Init event, other wise you will lose the properties value.
In Microsoft explanations about ASP.NET page life cycle, it is written that dynamically created controls must be created in PreInit.
It worked for me.
Here is my main page :
protected global::System.Web.UI.HtmlControls.HtmlGenericControl FiltersZone;
(. . .)
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
FiltersZone.Controls.Add(new PlanningFiltersSurgeonWeb());
}
This dynamically created ".ascx" control contains an hidden field :
<input id="hidTxtPaint" type="hidden" name="hidTxtPaint" runat="server" />
I am now able to retrieve its value from within dynamically created ASCX control Page_Load event, after a "submit" or a "__dopostback('hidTxtPaint')" initiated from JavaScript.
On the other hand, the hidden field's value is always empty after a POST if its parent ".ascx" control is added in main page's Page_Load event.
I've a asp:TextBox and a submit button on my asp.net page. Once the button was clicked, the TextBos's value is posted back. I'm going to keep the the posted-back text value into session, so that other child controls can access to the value during their Page_Load. However, I always get NOTHING ("") in the Page_Load method, and I can read the text out in the button click handler. I know that the "button click event" happens after the Page_Load. So, I'm asking how can I "pre-fetch" the TextBox.text during Page_Load?
public partial class form_staffinfo : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e){
string s = staff_id.Text; //Reach this line first, but GET empty value. However, I want to keep it in the session during this moment.
}
protected void btn_submit_Click(object sender, EventArgs e) {
string s = staff_id.Text; //Reach this line afterward, value got.
}
}
-- EDITED --
<%# Control Language="C#" AutoEventWireup="true" CodeFile="form_staffinfo.ascx.cs" Inherits="form_staffinfo" %>
<asp:Label ID="Label1" runat="server" Text="Staff ID: "></asp:Label>
<asp:TextBox ID="staff_id" runat="server" ></asp:TextBox>
<asp:Button ID="btn_submit" runat="server" Text="Query" OnClick="btn_submit_Click" />
Since I can't get the TextBox's text in the Page_Load, so, I didn't include any code related to session for clear presentation.
Thank you!
William
None of the values of your server controls are available for consumption in the Page_Load. Those controls are assigned after the form is validated (which is after the form is loaded) and before the form's control's events fire (like button clicks, in your example). The values posted are in the Request.Form Collection. Look in the AllKeys property and you should see a key that ends in $staff_id if you use your example posted. There may be other characters in from of the key, depending upon if the control is nested in a master page or other control.
If you absolutely must have that value at page load, grab it from the Request.Form collection instead of the user control, but I would question the wisdom of capturing the value that early in the page lifecycle. You could conceivably capture the textbox's OnTextChanged Event if you needed to preserve the value in Session.
EDIT - Additional Explanation
if you were going to create a custom event for your user control, there are only a couple of steps to it.
Create a delegate. This is will be the common object for inter-control messaging.
public delegate void StaffIdChangedEvent(object sender, string staffId);
Declare an event using that delegate in the user control that is going to broadcast.
public event StaffIdChangedEvent StaffIdChanged;
In your user control, when you are ready to broadcast (say from the Staff_id textbox's OnTextChanged event), you just invoke the event [Its generally a best practice to check to see if the event is null]
this.StaffIdChangedEvent(this, "staff-id-value-here");
The final step is to wire the user control event up to an event handler (this prevents the null situation I mentioned above when trying to invoke the event). You could wire a handler into the hosting page.
this.form_staffinfo.StaffIdChangedEvent += this.some_method_on_page;
Just make sure the method on the page has the same method signature as the delegate used to declare the event.
Events also could be wired into each control that needs to know about them (look up multicast delegates), so you could do something like:
this.form_staffinfo.StaffIdChangedEvent += this.some_method_on_page;
this.form_staffinfo.StaffIdChangedEvent += this.some_control_on_the_page;
this.form_staffinfo.StaffIdChangedEvent += this.some_other_control_on_the_page;
In any event, I generally preferred to do this type of wiring in the page's OnInit method.
override protected void OnInit(EventArgs e)
{
base.OnInit(e);
InitializeComponent();
}
and just write your own InitializeComponent method to centralize any of this wiring you have to do.
There is something else that is setting the textbox value. Could you please check if you are overriding other event that occurs before Page_Load and modifying the textbox text property. Even, posting the code where you update session variable would be handy. From the code you have posted, it should work.
Do you have autoeventwireup disabled? I could be mistaken, but I think if it is disabled your Page_Load will not fire. If you want to leave it disabled, you can always override the OnLoad event...
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// do stuff
}
I have a problem regarding to asp.net lifecylce hierarchy.
Basically, I have one user control which has a GridView in it. And this GridView is dynamically generated based on a public property on the control (named Parameter in the simplified code below).
Everything is fine when I insert this control in an aspx page, set its Parameter property and call DataBind(Parameter) on it. GridView is generated and populated on the UI.
Problem occurs when I post-back the page. I have to regenerate the GridView structure so that the data in the ViewState of the control can be used to populate the GridView. So that I can achieve its content. But as long as the GridView structure is generated dynamically and it is based on the Parameter property set on it, this is not possible. Because the OnInit of the user control is called before the OnInit of the page and that is why the Parameter property is set after the generation of the GridView structure. As a result I get an empty Gridview in the end.
Here is the code in a simplified way.
Can you give me some recommendation how to overcome this?
Can I explicitly force asp.NET to re-load the ViewState of a gridview?
Page HomePage.aspx has an OnInit event handler in such a way, where it sets a property of the user control ctlMyUSerControl
protected override void OnInit(EventArgs e)
{
ctlMyUserControl.Parameter = new Parameter()
name="Orhan",
surname= "Pamuk"};
}
And in ctlMyUserControl's OnInit I have
protected override void OnInit(EventArgs e)
{
if (Page.IsPostBack && Parameter !=null && SomeGridViewRowsExistOnUI)
{
// Generate dynamic columns based on Parameter property
// So that gridview can be populated
// with the post-backed data which
// should contain the ViewState of the gridview
GenerateGridViewColumns(Parameter);
}
base.OnInit(e);
}
I have sold it guys.
What I have done is regenerating my GridView columns on the container page of the user control.
So, On the OnInit of the page I regenerate my columns and it is still before the call of LoadViewState() method.
protected override void OnInit(EventArgs e)
{
Parameter parameter = new Parameter()
name="Orhan",
surname= "Pamuk"};
ctlMyUserControl.GenerateGridViewColumns(parameter);
}
I have a GridView with dynamically created image buttons that should fire command events when clicked. The event handling basically works, except for the very first time a button is clicked. Then, the postback is processed, but the event is not fired.
I have tried to debug this, and it seems to me, that the code executed before and after the first click is exactly the same as for any other clicks. (With the exception that in the first click, the event handler is not called.)
There is some peculiarity in that: The buttons which fire the event are created dynamically through databinding, i.e. databinding must be carried out twice in the page lifecycle: Once on load, in order to make the buttons exist (otherwise, events could not be handled at all), and once before rendering in order to display the new data after the events have been processed.
I have read these posts but they wouldn't match my situation:
ASP.NET LinkButton OnClick Event Is Not Working On Home Page,
LinkButton not firing on production server,
ASP.NET Click() event doesn't fire on second postback
To the details:
The GridView contains image buttons in each row. The images of the buttons are databound. The rows are generated by GridView.DataBind(). To achieve this, I have used the TemplateField with a custom ItemTemplate implementation. The ItemTemplate's InstantiateIn method creates the ImageButton and assigns it the according event handler. Further, the image's DataBinding event is assigned a handler that retrieves the appropriate image based on the respective row's data.
The GridView is placed on a UserControl. The UserControl defines the event handlers for the GridView's events. The code roughly looks as follows:
private DataTable dataTable = new DataTable();
protected SPGridView grid;
protected override void OnLoad(EventArgs e)
{
DoDataBind(); // Creates the grid. This is essential in order for postback events to work.
}
protected override void Render(HtmlTextWriter writer)
{
DoDataBind();
base.Render(writer); // Renews the grid according to the latest changes
}
void ReadButton_Command(object sender, CommandEventArgs e)
{
ImageButton button = (ImageButton)sender;
GridViewRow viewRow = (GridViewRow)button.NamingContainer;
int rowIndex = viewRow.RowIndex;
// rowIndex is used to identify the row in which the button was clicked,
// since the control.ID is equal for all rows.
// [... some code to process the event ...]
}
private void DoDataBind()
{
// [... Some code to fill the dataTable ...]
grid.AutoGenerateColumns = false;
grid.Columns.Clear();
TemplateField templateField = new TemplateField();
templateField.HeaderText = "";
templateField.ItemTemplate = new MyItemTemplate(new CommandEventHandler(ReadButton_Command));
grid.Columns.Add(templateField);
grid.DataSource = this.dataTable.DefaultView;
grid.DataBind();
}
private class MyItemTemplate : ITemplate
{
private CommandEventHandler commandEventHandler;
public MyItemTemplate(CommandEventHandler commandEventHandler)
{
this.commandEventHandler = commandEventHandler;
}
public void InstantiateIn(Control container)
{
ImageButton imageButton = new ImageButton();
imageButton.ID = "btnRead";
imageButton.Command += commandEventHandler;
imageButton.DataBinding += new EventHandler(imageButton_DataBinding);
container.Controls.Add(imageButton);
}
void imageButton_DataBinding(object sender, EventArgs e)
{
// Code to get image URL
}
}
Just to repeat: At each lifecycle, first the OnLoad is executed, which generates the Grid with the ImageButtons. Then, the events are processed. Since the buttons are there, the events usually work. Afterwards, Render is called, which generates the Grid from scratch based upon the new data. This always works, except for the very first time the user clicks on an image button, although I have asserted that the grid and image buttons are also generated when the page is sent to the user for the first time.
Hope that someone can help me understand this or tell me a better solution for my situation.
A couple problems here. Number one, there is no IsPostBack check, which means you're databinding on every load... this is bound to cause some problems, including events not firing. Second, you are calling DoDataBind() twice on every load because you're calling it in OnLoad and Render. Why?
Bind the data ONCE... and then again in reaction to events (if needed).
Other issue... don't bind events to ImageButton in the template fields. This is generally not going to work. Use the ItemCommand event and CommandName/CommandArgument values.
Finally... one last question for you... have you done a comparison (windiff or other tool) on the HTML rendered by the entire page on the first load, and then subsequent loads? Are they EXACTLY the same? Or is there a slight difference... in a control name or PostBack reference?
Well I think the event dispatching happens after page load. In this case, its going to try to run against the controls created by your first data-binding attempt. This controls will have different IDs than when they are recreated later. I'd guess ASP.NET is trying to map the incoming events to a control, not finding a control, and then thats it.
I recommend taking captures of what is in the actual post.
ASP.NET is pretty crummy when it comes to event binding and dynamically created controls. Have fun.
Since in my opinion this is a partial answer, I re-post it this way:
If I use normal Buttons instead of ImageButtons (in the exact same place, i.e. still using MyItemTemplate but instantiating Button instead of ImageButton in "InstantiateIn", it works fine.
If I assert that DoDataBind() is always executed twice before sending the content to the client, it works fine with ImageButtons.
Still puzzled, but whatever...
I have a web user control (ascx) that exposes an "ID" property. What I want to do is set this property when the SelectedIndexChanged event fires in a gridview in the containing page. However, I can't seem to do it.... Here's my code:
protected void grdPhysicians_SelectedIndexChanged(object sender, EventArgs e)
{
physicians_certif1.mdID = grdPhysicians.SelectedDataKey.ToString();
mvData.SetActiveView(viewEdit);
panAdditional.Visible = true;
}
Physicians_certif1 is the user control. It seems the user control is loading before the SelectedIndexChanged event has a chance to set it's property.
Any ideas folks?
ASP.Net page lifecycles can be hard to understand especially with ascx user controls which also have their own lifecycle. If you are setting the mdID property in Page_Load of either the page or the ASCX control or have hardcoded a default value into it in the XHTML, it is probably being reset after SelectedIndexChanged fires.
Set a breakpoint in grdPhysicians_SelectedIndexChanged, set a watch on physicians_certif1.mdID and step through the code using the debugger.
Yes, that is exactly what is happening. You should look at (and be familiar with) the following resource:
ASP.Net Page Life Cycle
The page will load, then the control will load, then your events will begin to fire. If you have configuration needs based on event triggers, it is best either to place those configurations in the Page_LoadComplete or Page_PreRender events of the user control in question or apply "Rebinding" instructions in the Set method of your property:
public MyValue MyProperty()
{
get
{
return _myProperty;
}
set
{
RebindMyControls();
_myProperty = value;
}
}