Ok time to show my complete lack of knowladge for all things web forms but here goes. I am extending the Panel control and OnPreRender sticking some additional controls inside of it (lets just say 1 textbox for simplicity). From here I am just letting the Panels Render method do its thing.
The issue I am having is that obviously every time this control is rerendered it is just sticks that same TextBox in the panel again with the value I am coding in the OnPreRender method. Now I dont actually want to repopulate the panel every time,
I want to stick the textbox contorl in there on first load and have them reloaded from the control/viewstate caches. In this case with my example of just sticking a single textbox in the panel, if the value of the textbox changes and a postback occurs I want that value to to remain the changed value.
Really basic webforms stuff I know, but I have never had to create custom controls in my time. ANy help appreciated.
Chris.
You need to (re)create the child control (the textbox) in OnInit - so that it's there when LoadViewState and ProcessPostBackData is called.
See the server control lifecycle for more info.
Dynamic controls in ASP.NET are tricky, especially if you are new to webforms and the page lifecycle. If you can avoid dynamic controls, do so. Use controlName.Visible=false, and other tricks instead.
If you must then read this article. Rule of thumb,add controls early in the page life cycle, reference them later in the page lifecycle. PreRender is almost the very end, an uncommon place to be adding and using controls.
Not sure if this applies to all versions of .Net, (I think 2.0 or later) but there is a method called CreateChildControls that isn't really a part of the lifecycle exactly, it's basically called anytime the EnsureChildControls method is called. By default it is called before PreRender if it's not a postback. So basically your code would look like this:
public class SomeControl : WebControl, INamingContainer
{
private TextBox someTextBox;
protected override void CreateChildControls()
{
base.CreateChildControls();
someTextBox= new TextBox();
someTextBox.ID = "tbxMain";
Controls.Add(textboxToCheck);
}
}
Now the part to not is that unless you call EnsureChildControls, you can't be 100% sure that the controls exist before the Public Properties on your control are filled by the ViewState load. What does this mean? Well take the code from before and add a property for the CssClass:
public class SomeControl : WebControl, INamingContainer
{
private TextBox someTextBox;
protected override void CreateChildControls()
{
base.CreateChildControls();
someTextBox= new TextBox();
someTextBox.ID = "tbxMain";
Controls.Add(textboxToCheck);
}
public String CssClass { get; set; }
}
In CreateChildControls you won't want this:
someTextBox.CssClass = CssClass;
Since there is no way to be sure the control exists yet. There's a couple ways you can handle this:
public String CssClass
{
get
{
EnsureChildControls();
return someTextbox.CssClass;
}
set
{
EnsureChildControls();
someTextbox.CssClass = value;
}
In this example I am calling EnsureChildControls (Assuming you are setting the CssValue on the textbox in the CreateChildControls method) and setting or getting from the textbox.
Another way is putting anything that depends on the control's public properties in the OnPreRender method:
protected override void OnPreRender(EventArgs e)
{
someTextbox.CssClass = CssClass;
}
Thus avoiding the mess of worrying about the property being filled already during the ViewState load.
One Note:
The use of INamingContainer can be important. Basically all that does is makes sure the controls on the parent control have an id that is unique on the page by applying the parent's name (And maybe more) to the id. Basically if the parent ID is Parent and the child control ID is Child the ID might show up as Parent_Child. This will solve problems with ViewState not populating the properties correctly or not at all.
Inside your code you will need to manage the restore of viewstate information should you need the services of viewstate.
A good example here is this View State example by Microsoft. There are a few other items referenced in the code sample, but it should get you along the right path.
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 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;
}
}
I've got a rather lengthy question I'm afraid. I'm fairly new to ASP.NET so please bear with me.
I have built a control for an ASP.NET page that lists a number of options. Each option has two clickable areas (call them buttons for the sake of simplicity). One to select the option and one to hide the option.
protected void Page_Load(object sender, EventArgs e)
{
RenderOptions();
}
public void RenderOptions()
{
for (int i = 0; i < 5; i++) {
HtmlGenericControl div1 = new HtmlGenericControl("div");
div1.Attributes.Add("onclick", ClientScript.GetPostBackEventReference(this, "option" + i));
m_TreeContainer.Controls.Add(div1);
HtmlGenericControl div2 = new HtmlGenericControl("div");
div2.Attributes.Add("onclick", ClientScript.GetPostBackEventReference(this, "option" + i));
m_TreeContainer.Controls.Add(div2);
}
}
public void RaisePostBackEvent(string arg)
{
//do something
}
This works fine (I do implement the IPostBackEventHandler interface). The problem here is that there doesn't seem to be a way for me to find which HTML element was clicked and thus which action should be performed in the RaisePostBackEvent method.
What I tried to do is create a new class (HtmlDivControl) which looks like this:
class HtmlDivControl : HtmlGenericControl, IPostBackEventHandler
{
#region Delegates
public delegate void ClickEventHandler(object sender, string eventArgument);
#endregion
#region Properties
private ClickEventHandler m_Click;
public ClickEventHandler Click
{
get { return m_Click; }
set { m_Click = value; }
}
#endregion
#region Constructors
public HtmlDivControl()
{
}
#endregion
public void RaisePostBackEvent(string eventArgument)
{
m_Click.Invoke(this, eventArgument);
}
}
Now I made div1 and div2 my HtmlDivControl rather than HtmlGenericControl, set the Click property to a method (delegate) and passed the div (div1 or div2) itself as control for the GetPostBackEventReference method. This time, I could not only differentiate between the divs but also pre-determine the action that should be performed. However, the RaisePostBackEvent for controls are called after PageLoad. So the problem I'm with now is that the whole options control is rendered before the events are handled (and thus, an option that should for instance be hidden isn't because the actual hiding happens after the rendering). Moving the RenderOptions() call to the PageLoadComplete method doesn't help either, since then the div controls won't exist yet.
I'm pretty sure I'm missing something quite fundamental here. But could someone please explain me how I should approach something like this?
p.s.
How am I supposed to write underscores here? They're used to make text italic? Is there some escape character?
For someone new to ASP.Net, you've done pretty well so far. Your roadblock here is actually the way you are thinking about the issue. You should get a good grasp of the ASP.Net Page Lifecycle - you are missing something very fundamental.
In a nutshell, you want your page to rebuild it's state to the same way it was before the postback. Then process your events. Then make any state changes.
You're thinking about it as if your html controls should know about their state change at the start of the request, which is incorrect. There has to be the rebuilding phase first. This is critical for ASP.Net to even figure out which events to raise.
What I would recommend:
move your "RenderOptions()" method to the Page_Init handler. This will save you lots of issues if you ever incorporate ViewState into your controls. (I would also rename it, as it's not truly rendering anything, it's just adding your controls to the page. Render has a specific context in ASP.Net).
Then, in your OnClick event handlers for your controls, simply set your controls visibility as necessary, rather than trying to control the way they are rendered. It is always much simpler to set controls to Visible=False rather than try to change the way the controls are being rendered to the page. Remember that if you set Visible=False, there will be zero html sent to the response for that control, but the server will still know it's on the page, so you can still deal with it.
Think about your event handlers as the place where you will change the state of the page. It's where your logic should be in this case, rather than in Page_Load.
The setup: Web form with lots of TextBox controls.
When I set any one of the TextBox control's Enabled property to False, I'd like to "swap" that TextBox out for a label at runtime. The idea here being if it's read only anyway, don't display it in a control designed for editing.
I'm thinking this should be pretty simple and reusable, but what's the best way to do this?
Not sure its the best way, I would make a custom server control is a textbox,
then override the render method, check if it is readonly,
if it is read only then render your span tags like a label controls does.
if not then let the base( textbox ) render take over...
public class SpecialTextbox : TextBox
{
public override void RenderControl(HtmlTextWriter writer)
{
if (!this.ReadOnly)
{
base.RenderControl(writer);
}
else
{
writer.Write(string.Format("<span id=\"{0}\" class=\"{1}\">{2}</span>",
this.ClientID,
this.CssClass,
this.Text));
}
}
}
One possible solution would be to create a new control extending TextBox. Your specialized control would then override (parts of) the rendering code, causing the control to render similar to a Label when ReadOnly = true.
Another way would be to look into using a control adapter. You would essentially be able to do the exact same thing that BigBlondeViking reccomends, but you could coninue to use a regular asp:textbox control in your code. That will be much easier on you and other developers.
About control adapters
I need to use objChildControl.RenderControl or objControl.RenderChildren to manually render my child controls. But it looks like these methods are incomplete.
All my child controls use the OnPreRender event to register clientscript and client stylesheets (since these can only be created in the prerender event).
I have 2 main issues, passing the current System.Web.UI.Page object to a child control and making sure the OnPreRender event is fired on these child controls.
It seems that I can't use the RenderControl method on my child controls since the OnPreRender event will not be called.
I can however pass the Page object by objChildControl.Page = Me.Page
When I use RenderChildren I cannot pass the Page object, or can I?
And i'm not sure if the OnPreRender event is even called when I use RenderChildren.
Some help would be appreciated, since i'm stuck ;)
Update
I found a way to get the result I need, but it is not the solution I want.
Example:
Code I want:
<wc:ParentControl id="objParent" runat="server" bla="etc">
<Content> <!-- This is an InnerProperty of the ParentControl --><DIV>bla bla bla bla.....<wc:SomeControl id="objSomeControl" runat="server" /><wc:3rdPartyControl id="obj3rdPartyControl" runat="server" /></DIV></Content>
</wc:ParentControl>
CodeBehind: objParentControl.Content.RenderControl(Writer)
And then the issues mentioned above will begin. How to make sure that for all the children within Content the OnPreRender will be called?
Code which does work (but then the RenderControl method is just useless):
<wc:ParentControl id="objParentControl" runat="server"></wc:ParentControl>
<wc:Content id="objContent" runat="server"><DIV>bla bla bla bla.....<wc:SomeControl id="objSomeControl" runat="server" /><wc:3rdPartyControl id="obj3rdPartyControl" runat="server" /></DIV></wc:Content>
Then just use the RenderBeginTag and RenderEndTag of the wc:Content control.
Then the OnPreRender event is called.
But I wan't to embed the content into the parentcontrol by using an InnerProperty.
And then manually rendering the childcontrols by RenderControl or RenderChildren.
I had a similar issue. I'm not sure if it's the same issue you're experiencing, but the problem I was having was that I had a ParseChildren(true) attribute on my container control. Because ParseChildren was true, the child controls would be put into a property, rather than into the containing control's child controls collection, and would never get their OnPreRender function called.
I ended up overriding the CreateChildControls function in my containing control class, where I added everything from my parsed collection to the Controls collection. Because I'm overriding the Render function anyway, I don't worry about the controls in the Controls collection being rendered when I didn't want them to be.
Something like below:
[ParseChildren(true, "MyKids")]
public class Example : Control {
private ArrayList _kids = new ArrayList();
public ArrayList MyKids {
get { return _kids; }
set { _kids = value; }
}
protected override CreateChildControls() {
Controls.Clear();
foreach(Control c in _kids)
Controls.Add(c);
}
protected override Render(HtmlTextWriter writer) {
...
}
}