I'm trying to build a CompositeControl that's flexible. Depending on some of it's parameters, I would like my CompositeControl to load different user controls in it's CreateChildControls method. The exact UserControls aren't know at design time.
Just as a quick example, I tried with a "hard coded" UserControl and it failed:
protected override void CreateChildControls()
{
Control UserControlControl = Page.LoadControl(typeof(MyUserControl), null);
Controls.Add(UserControlControl);
Label RegularControl = new Label();
RegularControl.Text = "This gets displayed";
Controls.Add(RegularControl);
}
Is it possible to attain what I'm looking for?
Thanks
Try the following:
protected override void CreateChildControls()
{
Control UserControlControl = Page.LoadControl("~/path/to/control.ascx");
Controls.Add(UserControlControl);
}
Related
I have a user control (Navigation) nested within another user control (Header) that is dynamically loaded from a Control class (Standard).
The user controls, Navigation and Header have AutoEventWireup = false.
The control class Standard calls loads the Header user control from a configuration item.
private void layoutAndRender(HtmlTextWriter output, string UserControlKey, NameValueCollection UserControlsConfiguration)
{
if(UserControlsConfiguration[UserControlKey] != null && UserControlsConfiguration[UserControlKey].ToString() != "")
{
string suc = System.Web.HttpContext.Current.Request.ApplicationPath + UserControlsConfiguration[UserControlKey].ToString();
UserControl ucToRender = (UserControl)this.Page.LoadControl(suc);
ucToRender.RenderControl(output);
}
}
My problem is that I want to initialize an object in the Navigation user control that can accept Page.Request and Page.Response, but events don't seem to be firing in the Navigation code behind.
The code I'm using to initialize my object is:
this.browser = new Browser(this.Request, this.Response);
I tried doing this during the Navigation constructor but this.Request and this.Response are not set at that time.
I tried using the statement in a void Page_Load(object sender, System.EventArgs e) method, but this doesn't seem to be firing, even if I have this.Load += new System.EventHandler(this.Page_Load); in the Navigation constructor.
I've also tried similar statements for Page_Init and Page_PreRender, but none of these seem to be firing.
Is it that a control loaded with LoadControl does not fire Load or Init events, if loaded the way I have loaded them, and the same goes for any user controls that it may include?
If AutoEventWireup is set to false for the controls that you want to load, then you should override the OnInit method to wire up the Load event handler for the controls. The Request and Response properties should be available from within Page_Load.
For example:
public class Header : Control
{
private void Page_Load(object sender, EventArgs e)
{
}
override protected void OnInit(EventArgs e)
{
this.Load += new System.EventHandler(this.Page_Load);
}
}
See MSDN for more info on AutoEventWireup:
http://support.microsoft.com/kb/324151
Unfortunately, this is legacy code, and not always done the right way.
In particularly, because the user control is loaded and rendered manually, it probably skips most of the event model that I wanted to take advantage of. Ideally, it should have done an AddControl() to the page, rather than rendering the control to a HtmlTextWriter.
My work around is to override the RenderControl method, and initialize the browser property before passing RenderControl up the chain.
I am trying to find all asp:Image controls in the vb.net code behind to dynamically set the ImageUrl to the same image file. This I can do seperately for each control, but writing 10+
imgQuestion.ImageUrl = cdn.Uri.ToString & "images/question.png" lines seems a little silly. I do not need to skip any image controls - every single one on the page will be changed. Is there any way to identify all of them without specifying each ID?
The IDs are not all named something similar, such as "Image1", "Image2" but rather "PaymentNote", "search", etc so I cannot loop through all the numbers with something like FindControl("Image" & controlNumber)
Is there another way to do this? I'd prefer to keep the image control IDs as something meaningful.
You can recursively use FindControl, starting from the Page and for each control check if it's an <asp:Image...
My own preferred language of choice is C#, so I won't be able to show a VB example. But here's a C# example:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ChangeImageUrls(Page);
}
private void ChangeImageUrls(Control ctrl)
{
foreach (Control subCtrl in ctrl.Controls)
{
if (subCtrl is Image)
{
((Image)subCtrl).ImageUrl = "...";
}
if (subCtrl.HasControls())
ChangeImageUrls(subCtrl);
}
}
}
I present to you a little mystery... the following control is intended to fail validation every time, no matter what, but it does not:
public class Test : CompositeControl
{
protected override void CreateChildControls()
{
Controls.Clear();
CreateControlHierachy();
ClearChildViewState();
}
void CreateControlHierachy()
{
var validator = new CustomValidator
{
ErrorMessage = "Can't do that!"
};
validator.ServerValidate += (sender, e) =>
{
e.IsValid = false;
};
Controls.Add(validator);
}
}
To "fix" the issue, add the following line to CreateControlHierachyand all works as expected:
Controls.Add(new TextBox());
The control is registered in the web.config and placed on a simple page like this:
<uc:Test runat="server" />
Using the debugger on a post back event reveals the following:
The validator is in the control hierachy on the page, as expected.
The validator is not registered in Page.Validators.
Both Page.IsValid and validator.IsValid are still true.
What effect is the TextBox having on the validator and what is the correct way to fix this?
I found a possible explanation for this. The presence of the TextBox adds a child control to your control that is an IPostbackDataHandler. In order to load the post data the page must first find the control which of course it does by calling FindControl. As FindControl does its thing it eventually accesses the Controls collection of your control. Because your control is a CompositeControl this calls EnsureChildControls which call CreateChildControls.
All of this happens before Validation. Take out the TextBox and the Controls collection is no longer accessed before validation and therefore the validator is not created until after validation (most likely during prerender)
Since your validator doesn't exist at the validation stage it doesn't get called. I recommend adding a call to EnsureChildControls before validation occurs.
protected override void OnLoad(EventArgs e)
{
EnsureChildControls();
base.OnLoad(e);
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
.Net Changes the element IDs
I have the following problem (I'll explain it simplified because otherwise it would get too complicated/long).
I need to create an ASP.net server control that is inherited from Panel. Simple enough. When the custom control renders, it should dynamically create a Button, associate an event handler to it (which will also be defined inside the server control) and add this button to the rendered control. The click event handler defined inside the server control does some job which for the moment isn't interesting.
I already coded an example and that works fine. In the constructor of the server control I create a new button control, give it an ID, associate an event handler, on the OnInit of the server control I add the button to the Panel controls (remember, my control inherits from Panel) and then everything gets rendered. This looks something like the following:
public class MyCustomControl: Panel
{
private Button myButton;
public MyCustomControl()
{
myButton = new Button();
myButton.ID = "btnTest";
myButton.Click += new EventHandler(btnTest_Click);
}
protected void btnTest_Click(object sender, EventArgs e)
{
//do something...
}
//...
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.Controls.AddAt(0, myButton);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//...
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter output)
{
base.RenderContents(output);
}
}
This works quite fine. The button gets rendered correctly and when I click on it, the appropriate handler inside this customer server control is invoked and executed. Now the problem however starts when I would like to add multiple instances of this server control to my page because all of my generated buttons will have the same id "btnTest" which results in a conflict. So what I tried is to associate a random number to the id of the button, s.t. it end up being "btnTest1235512" or something similar. This solves my problem with the multiple IDs, but results in the problem that my event handler when clicking on the button is no more called correctly. I guess this is due to the problem that my button always gets another id when entering in the constructor and so the appropriate callback (btnTest_Click event handler) isn't found.
Can someone give me a suggestion how I could handle the problem. Is there some way for remembering the ID of the button and re-associating it. As far as I know however this has to happen in the OnInit, where the ViewState isn't yet available. So storing the id in the ViewState wouldn't work.
Any suggestions??
Implementing INamingContainer should fix the problem. You can then keep naming all your buttons btnTest.
This is just a marker interface so you don't have to implement any methods or properties.
Inherit from INamingContainer like:
public class MyCustomControl : Panel, INamingContainer
{
}
This is a marker interface and requires no implementation. I just verified with your code and it solves your problem.
Implementing INamingContainer will do it for you. It will automatically name the buttons with unique ID's. Just a note, if you need to use any of those buttons in any JavaScript, you'll need to use the ClientID property:
function getButton() {
var myButton = document.getElementById('<%=btnTest.ClientID %>');
}
This is partly in reference to this:
Why isn't the SelectedIndexChanged event firing from a dropdownlist in a GridView?
I thought it different enough to ask another question.
My thought is that instead of adding a dropdownlist (ddl) to a gridview and then using the technique above that I could create a brand new control that has a ddl in it and the reference it directly.
This is more of a how do I create asp.net 2.0+ controls, I think, but is what I am asking possible? Can you make a "new" gridview control that just happens to always have a ddl in it and just refer to it (somehow) without findcontrol and all the rest?
I realize it would be highly customized for a unique app. I am just trying to see if it is possible as I may want to use this to create other controls.
Thank you.
It depends on your definition of a "new GridView". The answer is yet, but at a cost.
If you base your control on WebControl, you can write a new grid control with any functionality. Somehow, I don't think this is what you have in mind.
If you want to inherit from the existing GridView and add extra controls, then it is also doable, but with heavy limitations. The reason is because GridView's implementation breaks every possible guideline for extensibility. I guess because they never meant it to be extended. For instance, they clear Controls collection almost on every occasion and explicitly expect for the Controls[0] to be a Table. I suppose, if you decide to stay within confines of the table layout (header, footer and all), then you may have more room to play.
Finally, you could create a wrapper, which has a GridView as its private member and simply expose everything you may need plus more. But that gets ugly very quickly.
Here is a crude demonstration (working) of the second approach. Note that the drop down is at the end. You can override the Render method, but you'd have to recreate a lot of MS code.
ExtendedGridView
public class ExtendedGridView : GridView
{
protected DropDownList DropDown { get; set; }
public ExtendedGridView() : base()
{
this.DropDown = new DropDownList();
this.DropDown.Items.Add("white");
this.DropDown.Items.Add("red");
this.DropDown.Items.Add("blue");
this.DropDown.Items.Add("green");
this.DropDown.AutoPostBack = true;
this.DropDown.ID = "dropdown";
this.DropDown.SelectedIndexChanged += new EventHandler(DropDown_SelectedIndexChanged);
}
void DropDown_SelectedIndexChanged(object sender, EventArgs e)
{
BackColor = System.Drawing.Color.FromName(this.DropDown.SelectedValue);
}
protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
{
int itemCount = base.CreateChildControls(dataSource, dataBinding);
Controls.Add(this.DropDown);
return itemCount;
}
}
SomePage.aspx
<%# Register TagPrefix="my" Namespace="MyProject" Assembly="MyProject" %>
<my:ExtendedGridView id="myGridView" runat="server" onpageindexchanging="myGridView_PageIndexChanging"></my:ExtendedGridView>
SomePage.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
myGridView.DataSource = new string[] { "aaa", "bbb", "ccc", "ddd", "eee" };
myGridView.AllowPaging = true;
myGridView.PageSize = 2;
myGridView.DataBind();
}
protected void myGridView_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
myGridView.PageIndex = e.NewPageIndex;
myGridView.DataBind();
}