Abstract ASP.NET User Control without recreating markup - c#

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).

Related

Accessing Template items of UserControl

Is it possible to implement custom UserControl or Control, which will act like a PlaceHolder, but with my logic? Example of using:
<x:MyControl runat="server">
<Template>
<asp:TextBox runat="server" ID="MyTextBox" />
..... any custom code here ........
</Template>
</x:MyControl>
And then in code-behind:
MyTextBox.Text = "ABC";
I implemented test control, but I am unable to access nested ASP controls on the page level. Error is The name "MyTextBox" does not exist in the current context.
You must call the function "FindControl" in you user control MyControl.
Name you MyControl with and id="mcContainer", then on your code behind call ((TextBox)mcContainer.FindControl("MyTextBox")).Text = "ABC";.
Wiki about this here How to: Create Templated ASP.NET User Controls
Why can I write MyTextBox.Text="ABC" when it is in the Page and why isn't that possible when the text box is inside of naming container implemented by a user control?
Well, when you drag N dropped your text box the ASP.NET Page designer declared a variable for you in the partial class of your page reserved for the designer itself, is in that designer part of your class that your controls in you page are declared.
So when you drag and drop your user control, and create a text box inside of it's naming container you can't access it directly because your page doesn't hold a reference to that control, the reason for that is because that control will only be available at runtime during the instanciation of the user control itself, the motive is related in part with the way that ASP.NET page parser renders controls.
Quoting this resource:
When working with composite controls it is important to be familiar with a number of properties and methods, as well as with the INamingContainer interface. All composite controls should implement the INamingContainer interface. Controls that implement this interface do not need to add any methods or properties; rather, the implemented interface merely indicates that the control is being used as a composite control. The effect is that child controls—that is, controls in the composite control's Controls collection—are rendered so that their ID is prefixed with the ID of the control's naming container. This ensures that all the child controls will have unique ID values, even if there are multiple instances of the parent control on a Form. The WebControl class has a NamingContainer property that returns the control's parent.
So, when you write MyTextBox, your text box name should at least something like $mcContainer$MyTextBox. So because of this there is no way to transparently do what you intend.
Solution.
Create class like this:
public class ToolTip : PlaceHolder
{
protected override void CreateChildControls()
{
HtmlGenericControl div1 = new HtmlGenericControl("div");
div1.Attributes.Add("id", ClientID);
div1.Attributes.Add("class", "tooltip");
while (Controls.Count != 0)
div1.Controls.Add(Controls[0]);
Controls.Add(div1);
}
}
Then register it in ASPX file like this:
<%# Register Assembly="YOUR_ASSEMBLY" TagPrefix="x" Namespace="YOUR_NAMESPACE" %>
Then use it like this:
<x:ToolTip runat="server">
... any your content here ......
</x:ToolTip>
Now you are able to nest any controls inside of x:ToolTip and add any composition logic to CreateChildControls().

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

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.

Access Controls from UserControls in ASPX page

How to Access Controls from UserControls in ASPX page?
For example:
I want to access gridview which is in usercontrol on ASPX page.
Please help me.
Try this :
GridView GridView1 = (GridView)WebUserControl1.FindControl("GridView1");
Where WebUserControl1 is ID of use control on .aspx page.
Hope this helps..
The best way is to provide properties in your UserControl that you can access.
For example:
public GridView UserGrid
{
get
{
return GridView1;
}
}
But the question is why you need this.
Rule of thumb: Only expose as few as possible. On that way your code will be most robust and readable. So it would e.g. better to expose it's DataSource rather than the complete GridView.
On the other hand, if you want your page to react on events in your UserControl, it should provide custom events(e.g. UserDeleted) that your page then can handle.
Page-UserControl-Commmunication
Because the controls now nothing about eachother at the same level, you have to use the parent page to get it. Try this logic:
Create a property referring to your parent page (the page your control usually is in (might be different because of layering of controls)
Create a property on your page referring to the usercontrol the grid is in, or link to that grid directly.
page.aspx
public UserControl UserGridControl
{
get;
set;
}
userControl.ascx
public Page ParentPage
{
get;
set;
}
Example call:
Instantiate the properties first. After that, use the following statement to access anything from that control (as an example I used Foo() here, to use as a dummy method, but it seems it was unclear to someone):
otherControl.ascx:
this.ParentPage.UserGridControl.Foo();
EDIT: Want to have a direct code example to be able do it from the page only? See Tim Schmelters answer. If you need a way to call the grid from another user control as well. Use mine.

calling a method on the parent page from a user control

I am using a user control that I created (just a .cs file not an .ascx file), that does some magic and depending on a value generated by the control, I need it to do something on the parent page that is 'hosting' the control. It needs to call a method under certain circumstances (method is on the parent control).
the control is placed on the parent page like so:
<customtag:MyControl ID="something" runat="server" />
I'm dynamically creating buttons etc on the control itself but when a button is clicked, let's say for example that there's a text box on the control and if the value of the textbox is "bob" it needs to call a method on the page that's housing the control...how can I accomplish this?
You could do what casperOne suggested, but I wouldn't advise it. This is tightly coupling your user control to your parent page, which kind of defeats the purpose of a user control.
Personally, I'd add an event to the user control (say, ButtonClicked) that the parent page can handle. In the event handler in your parent, deal with the event however you see fit. This way you can plug the user control into a different page at a later date and not have to worry about logic in the user control that requires a specific kind of parent page.
You should be able to get the Page hosting the control through the Parent property. However, that's going to be returned to you as a Control. You have to cast it to the type of your page in order to access any methods on the page which you have defined.
I think that casperOne is on the right track, but you need to go a step further. I'm giong to make the assumption that this user control will be used on more then on page. (I normally write VB.Net, sorry if my C# is malformed)
Make a base page class (you can store it in your App_Code directory or in a project):
public class PageToInheritFrom : System.Web.UI.Page {
public void SpecialFunction() {
}
}
Now make sure that all of your pages inherit from this page in your code behind file:
public partial class _Default : PageToInheritFrom {
}
Now in your user control you know what the page type is and can call
((PageToInheritFrom)this.Page).SpecialFunction();

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.

Categories

Resources