Custom Control with Standard Child Controls - Why are their IDs null? - c#

I want to create a custom control ideally like this:
<foo:Frobnicate runat="server">
<DoStuffWithThese>
<asp:TextBox runat="server" ID="fizzbot" />
</DoStuffWithThese>
<!-- other controls can be in the wrapper but outside the DoSomeStuffWithThese collection -->
<asp:DropDownList runat="server" ID="othercontrol" />
</foo:Frobnicate>
although I would settle for this if necessary:
<foo:Frobnicate runat="server">
<DoStuffWithThese>
<asp:TextBox runat="server" ID="fizzbot" />
</DoStuffWithThese>
<AndOtherStuffWithThese>
<asp:DropDownList runat="server" ID="othercontrol" />
</AndOtherStuffWithThese>
</foo:Frobnicate>
I can access the controls in the codebehind OK (in the second, not-ideal example), but for some reason their IDs (which I need) are NULL.
Here is the codebehind:
[ParseChildren(true)]
public class Frobnicate : WebControl {
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<WebControl> DoStuffWithThese { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<WebControl> AndOtherStuffWithThese { get; set; }
public override OnLoad(EventArgs e) {
base.OnLoad(e);
foreach(Control currentControl in DoStuffWithThese) {
// the control can be accessed (e.g. I can see it's a TextBox, etc.
// but currentControl.ID == null here -- why? :(
}
}
Does anyone know why this is? And more importantly, how I can fix it so I can get a custom control in one of those formats and able to access the IDs?

I believe you have to implement INamingContainer in your class, WebControl doesn't do that on its own. Note that it's just a marker interface, you don't need any code. Also, instead of a list of controls, you should probably use ITemplate (which allows you to actually create the controls and use them in the output of your control).
This should be helpful: http://msdn.microsoft.com/en-us/library/36574bf6(v=vs.85).aspx
If this isn't what you want, please elaborate on what you're actually trying to do, and why you don't want to use templates.
To explain a bit more - just by adding a reference to a server control, you're not actually adding it anywhere to the page / control tree, which means it isn't actually created, and it doesn't work as part of the page, really (which includes a useful UniqueID/ClientID, and pretty much any logic except for the constructor).
When you have the template, you can use data binding to fill in the required data etc., and you can access the controls using FindControl, for example (or just use the Controls collection, but be aware that the template will likely also contain literals and other stuff than just your controls).

Can you get the id of the dropdown? If so, I think it has to do with the other items missing runat=server.

Related

Pass parameter into UserControl from Master page

Bear with me, first time with UserControls and this is a big application I'm inheriting...
I have a user control in a master page. That works. I need to reuse about 99% of it barring one conditional css class. My thought was to key off the ID and return the correct class.
.ascx:
<div class="<%= GetMyClass() %>">Changes colors!</div>
.ascx.cx:
public string GetMyClass(){
if (ID == "FirstId"){
return "first-class";
}
if (ID == "SecondId"){
return "second-class";
}
return "";
}
.master:
<slc:MyUsercontrol runat="server" ID="FirstId" />
... stuff
<slc:MyUsercontrol runat="server" ID="SecondId" />
Two things happened here.
First, with a breakpoint in my codebehind for the user control, it only breaks once, where I would expect it to break twice.
Second, the ID available in the codebehind is not the "ID" passed in to the page. If I change "FirstId" to "Slamalamadingdong" the value is still "FirstId" in the codebehind when it hits my breakpoint.
Note that "FirstId" was set when I inherited the project, I added the markup with "SecondId", so I'm supposing that this value is actually set up somewhere else completely and the markup value is irrelevant when we get to the codebehind.
Am I completely off base in how I'm trying to pull this off?
To pass a parameter into the UserControl, instead of relying on the ID expose a new property for the UserControl as so:
.ascx.cx:
public string MyClass { get; set; }
.ascx:
<div class="<%= MyClass %>">Changes colors!</div>
.master:
<slc:MyUsercontrol runat="server" ID="FirstId" MyClass="first-class" />
<slc:MyUsercontrol runat="server" ID="SecondId" MyClass="second-class" />
This is a much more generic approach since you can simply add other MyUsercontrol elements with their own behavior without changing MyUsercontrol's logic. Also coupling the UserControl logic with the ID from his parent is not a good idea.

Abstract ASP.NET User Control without recreating markup

I have the following control layout replicated several times in an ASP.NET application and would like to encapsulate the functionality in a user control (or server or composite control if necessary).
PSEUDO MARKUP
LISTBOX1 (Available Objects) > (Add Button) LISTBOX2 (Selected Objects)
Basically 2 listboxes with available objects in one listbox and selected objects in the other listbox (the type of these objects will change depending on use e.g. a collection of available and selected products)
Example usage: Add a listbox item from listbox1 to listbox2 via the add button (there are also buttons for adding all and going the other way from selected to available - these aren't shown for clarity) and the item is removed from listbox1 and placed in listbox2. Fairly straightforward - I'll call the control DualListbox.
I would like to do this:
DualListbox.ascx contains markup like this:
<asp:ListBox id="AvailableListBox" runat="server"/>
<asp:Button id="AddItemtoSelected" runat="server"/>
<asp:ListBox id="SelectedListBox" runat="server"/>
and DualListBox.ascx.cs contains a series of abstract and non abstract functions, properties etc.
e.g
--We don't actually use CollectionBase but a class derived from it
protected abstract CollectionBase AvailableItems {get;set;}
protected abstract CollectionBase SelectedItems {get;set;}
protected abstract void SaveContentsofAvailableListBox ();
private void FillAvailableListBox ()
{
.....
AvailableListBox.DataSource = AvailableItems;
AvailableListBox.DataBind()
}
private void MoveItemfromAvailabletoSelectedListBox()
{
..Some code that takes item from available and puts it in selected.
}
Then I'd inherit from DualListbox.ascx e.g.
ConcreteDualListBox : DualListBox
{
public override WidgetCollection AvailableItems {get;set;}
public override void SaveContentsofAvailableListBox(){}; etc.etc.
}
The problem is that you can't inherit markup and so the markup in DualListBox is unavailable to the concrete class. I can define the markup in the concrete class but then to use functions like FillAvailableListBox in the base class I would have to pass the AvailableListBox control (and all the other controls) into the base class from the concrete class.
Also for every concrete class the markup would have to be repeated (I could embed the common markup in each concrete class from another .ascx file I guess).
I would appreciate any suggestions on the correct way to go about defining such a control.
Apologies for the pseudocode - I'm doing this as a proof of concept at the moment.
Thanks,
Rich.
I don't think you can do this directly with a user control. What you can do is break this down into 2 controls, a user control and a regular server control. Keep the markup in the user control and have the server control override the OnInit method and load the user control and add it as a child. All the logic with abstract methods can be embedded in the server control which can be inherited.
To avoid layout issues you can create Templated ASP.NET User control. Here is an example from MSDN
I would suggest creating a function to generate the markup in the base class that child classes can call passing them the type of data they want to render/a template (this could even be a path to an ascx file)/a function that renders (child) data in the template (again in the child class).

Dynamically adding controls to a Templated User Control?

A few weeks back I created a Templated User Control, for the most part based on the example here:
http://msdn.microsoft.com/en-us/library/36574bf6(v=VS.90).aspx
The difference is that I did not implement the "MessageContainer" class as I wanted just an empty template that I could add whatever controls I wanted to at design time.
This TUC has been working great, but I ran into a scenario I hadn't anticipated when I created the thing. The need to dynamically add this TUC to a page, which means that I would need to dynamically add the controls within the template of the TUC as well.
I found another example here on how to dynamically create a Template and add it to the Templated Control:
http://msdn.microsoft.com/en-us/library/y0h809ak(v=vs.71).aspx
This second example article discusses only the "DataList, Repeater, and DataGrid controls" but I figure since I am using the ITemplate interface here, it should be the same thing.
However, I am unable to get this to work, I keep getting an "Object reference not set to an instance of an object." error when I attempt to populate the TUC.
Here's what I am doing....
Like the example above I created an ITemplate class:
public class XPCTemplate : ITemplate
{
private readonly Control _control;
public XPCTemplate(Control control)
{
_control = control;
}
public void InstantiateIn(Control container)
{
container.Controls.Add(_control);
}
}
Then, in the test page code-behind I attempt to load up and display the TUC dynamically:
ExpandCollapseRegion xpcRegion; // The Templated User Control
protected void Page_Load(object sender, EventArgs e)
{
PlaceHolder ph;
// .... code here to dynamically create some labels, textboxes, etc. ....
// Create an instance of the TUC
xpcRegion = new ExpandCollapseRegion();
// Pass into the TUC's template the controls dynamically created above
xpcRegion.LayoutTemplate = new XPCTemplate(ph);
// add the dynamic TUC to the page
phDynamicUC.Controls.Add(xpcRegion);
phDynamicUC.Controls.Add(new LiteralControl("<br />"));
}
Test page HTML Source:
<body>
<form id="form1" runat="server">
<div>
Dynamically Loading User Control
<br />
<asp:PlaceHolder ID="phDynamicUC" runat="server" />
</div>
</form>
</body>
When I run the page, I get the "Object reference not set to an instance of an object" error on the "container.Controls.Add(_control);" line of the XPCTemplate class. When I debug the test page and TUC control, the code of the TUC receives the XPCTemplate into its LayoutTemplate during the TUC's Page_Init() method, but when the InstantiateIn() event back in the XPCTemplate fires immediately afterwards, the "container" argument is NULL.
I'm not sure why this is happening, it's like the InstantiateIn() method of the XPCTemplate class is trying to actually set the PlaceHolder control within the TUC rather than just passing the contents. Maybe this is supposed to be the way and I am missing something on the TUC side to allow this behavior?
This is the first TUC I have created and likewise the first time trying to dynamically fill/load it, so I am sure I am missing something needed to accomplish this. Any help is greatly appreciated.
-- Andrew
Found the solution to the problem, which was how I was loading the TUC.
Incorrect:
xpcRegion = new ExpandCollapseRegion();
Correct:
xpcRegion = (ExpandCollapseRegion)LoadControl("ExpandCollapseRegion.ascx");
Making this simple change took care of the problem. Also, found that I could forego the need for a custom ITemplate class by using the CompiledTemplateBuilder() method. Really simplifies the code.
-- Andrew
I believe the problem is that you haven't instantiated your PlaceHolder ph that you are passing into your template. So, the line it is complaining on is because it can't add the control to the PlaceHolder because it is null/nothing.
Also, I believe you're going to need to put your placeholder somewhere. Otherwise, you're adding items to a container that is never added to the page.

How to add same behaviour to a usercontrol template as the gridview <Columns>

When defining the markup for an asp gridview and the tag Columns, one can only choose from a predefined set of controls to add within it (asp:BoundField, asp:ButtonField etc).
Im curious about if i can add the same type of behavior, say restricting the content to a custom control with the properties "Text" and "ImageUrl" to a TemplateContainer defined in a standard usercontrol and then handle the rendering of each element within the container from code behind somehow?
Alright i finally solved it, which means i can do the following
<%# Register src="~/Controls/Core/ContextMenu.ascx" tagname="ContextMenu" tagprefix="uc" %>
<%# Register Assembly="App_Code" Namespace="Core.Controls.ContextMenu" TagPrefix="cc" %>
<uc:ContextMenu ID="ContextMenuMain" runat="server">
<Items>
<cc:ContextMenuItem Text="New" ImageUrl="..." />
<cc:ContextMenuItem Text="Save" ImageUrl="..." />
</Items>
</uc:ContextMenu>
Where each ContextMenuItem is a custom class in app code, notice that i have to register the app_code assembly in order for the markup to recognize the class.
The namespace points to the location of the class.
For the code behind of the usercontrol we just add this:
private List<ContextMenuItem> items = new List<ContextMenuItem>();
[PersistenceMode(PersistenceMode.InnerProperty), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<ContextMenuItem> Items
{
get
{
if (items == null)
{
items = new List<ContextMenuItem>();
}
return items;
}
set
{
items = value;
}
}
Which can be processed by the usercontrol when its time to render :)
FYI The fields (asp:BoundField, asp:ButtonField etc) are not actually controls but are instead derived from the DatControlField class. Likewise, the columns property is not a ITemplate but is a DataFieldCollection.
Something like that should be possible if your controls all derive from the same class or implement the same interface.
I'm thinking maybe you want something like this:
http://www.developer.com/net/asp/article.php/10917_3609991_1
If you want to create a custom column type for a GridView, you need to start out by deciding what goes in each cell of the column. Is it just a TextBox or Button? Or is it something more complicated? If it's more complicated, you need to create your own template class by inheriting from ITemplate.
Once you have that done, you need to write a class that inherits from the proper column type (BoundField, ButtonField, etc. for simple columns or TemplateField if you need to plug in your own template).
You can create your own properties for your custom column class (e.g., Text and ImageUrl), but if it's a TemplateField, you will have to actually loop through your cells to manipulate the data of the controls that comprise each cell. This is not hard to do, but it's not as easy as just using a BoundField and setting DataField property.

Building Dynamic UI with C#?

I need to expose some input fields based on what properties I find for particular types in an assembly.
I'm not sure how common an approach like that is. Perhaps there are easier ways. Maybe on the client side instead of server.
If anyone knows of a good way of doing something like this, I would appreciate the help.
Create input controls accordingly and simple add control to some div container? I'm not sure if it would be more complex than that.
I'll need to somehow add css classes to the controls as I build them so they get placed nicely; that might get tricky.
This all sounds like standard asp.net development. Any good tutorial should be able to help you. For the asp server controls, you use the CssClass property to set the class for the control.
Here is the asp.net tutorial from the W3C Schools.
I assume you will use reflection to figure out what properties entity has, then you would based on the type of the property create an input field. You would have to dynamically create control to handle input in code behind. Make sure you give that control and id. You will have to recreate these controls on the post back. This looks to me like dynamic property editor. There might be some free ones, google for it.
If the UI doesn't have to be completely dynamic you could include all the controls in the markup with any optional ones set to Visible="false". Then, selectively enable the appropriate controls in your code-behind. For example:
Default.aspx
<asp:Button ID="EvenButton" runat="server" Text="Even" Visible="false" />
<asp:Button ID="OddButton" runat="server" Text="Odd" Visible="false" />
Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
String msg = "A message to count";
if (msg.Length % 2 == 0)
{
// Enable the Even Button
EvenButton.Visible = true;
}
else
{
OddButton.Visible = true;
}
}
The advantage of this method is that you can lay things out with the appropriate CSS easily in the markup. If, on the other hand, your UI is much more dynamic than this, you'll probably have to resort to dynamically creating controls in the code-behind and adding them to the page via calls to Controls.Add(). This way, however, is harder to layout. And you have to deal with things like re-wiring any event handlers on each postback.
Hope that helps.
I ended up leveraging jQuery.
I laid out a simple markup with the basic layout I would need.
For creating controls dynamically, I did it all in javascript using jQuery methods.
This of course requires that you return some data set to the UI intelligently enough to render it.

Categories

Resources