I have a WebControl library I'm using to render out our stylized form elements. The library also contains some overriden validators, so that when used together, I can render out some nicer validation HTML/CSS than the default validator's red-star-next-to-a-field. Namely, I'd like to highlight the entire line.
At its most basic level, the 'form line' could render out:
Outer <div class="hasError">
<asp:Label>
<asp:TextBox>
(From there, there's all sorts of extra whistles, AssociatedControlID...)
The issue I'm having is that when having the control create its own overriden validator (e.g. controlled with a IsRequiredField property), I'm unable to get the validator's ControlToValidate to target the control itself.
It would seem the validator's target
ControlToValidate needs to be at the same level in the control tree.
And I can't add it to the Parent's
control tree during
CreateChildControls, as it gives an error.
... So I have a class like this:
[DefaultProperty("Value"), ValidationProperty("Value")]
public class BasicTextInputLine : WebControl, INamingContainer
{
...
private Label _someLabel;
private TextBox _someTextBox;
private MyRequiredFieldValidator _someValidator;
public string Value
public bool IsRequiredField
...
}
(Note - The ValidationProperty("Value") flag allows a validator to point at it)
and
public class MyRequiredFieldValidator : RequiredFieldValidator
{
...
protected override void OnPreRender(EventArgs e)
{
...
if (foundControlToValidate is BasicTextInputLine)
(foundControlToValidate as BaseFormEntry).IsValid = false;
}
Which I can happily works in markup like so:
<custom:BasicTextInputLine ID="foo" ... />
<custom:MyRequiredFieldValidator ControlToValidate="foo" ... />
Issue:
Unfortunately, I have a catch-22 when adding things to the control tree:
Adding it to the parent (which would give a working control tree) gives an error about not being able to interfere with its control set.
protected override void CreateChildControls()
{
...
Parent.Controls.Add(_someValidator);
}
And adding it to the control gives an error about the ControlToValidate not being able to be found.
protected override void CreateChildControls()
{
...
Controls.Add(_someValidator);
}
How can I structure this so I can have BasicTextInputLine create its own validator, and have it validate itself, not its inner TextBox?
Much appreciated.
I'm not sure whether I fully understand the problem, but as I interpret your code snippets, you are mixing two concepts here:
The BasicTextInputLine contains the validator directly. I derive that information in particular by the variable in the class:
private MyRequiredFieldValidator _someValidator;
and you trying to add it to the list of child controls.
The validator is a completely separated control, which refers to the main BasicTextInputLine. I can see that in your markup:
I think these two approaches are contradictory and you have two options:
Stick to approach 1. In that case, you should create the validator instance in your code, and remove the markup part.
Stick to approach 2. In that case, the BasicTextInputLine class does not need to know about the validator, i.e., remove the variable inside the class and don't create it as child control. You simply do not need it.
Related
I am trying to make it so we have one code behind for a user control but different sites (and quite possibly pages) will need their own HTML template of that HTML. The html also needs to be modifyable by front-end developers without a dll push.
Previously, I have done this by creating a standard ascx with code behind that binds all the controls. I then have the front end developers make a copy of the ascx and change the markup of the new ascx (leaving the code behind definitions exactly matching from the original). Then have a setting that tells the control which actual ascx to use, and everything hooks up correctly.
However, I am wondering if there are better ways to achieve this, especially since something as simple as deleting an asp control (like a label) that's not needed for one site will cause an exception)
In an ideal world I would prefer something more akin to MVC or MVP type of system, but I am stuck in pure, original webforms system that I need to add custom controls to.
Is there a better way to achieve templating than creating multiple ascx's sharing the same code behind?
Note that physical files for the templates are a must, so that we can track the templates in source control.
Given your constraints, I think you're on the right track with sharing the code-behind. One thing that sticks out in your requirements is the ability delete a control without breaking the code-behind. You could achieve this by adding an intermediate code-behind between the base class (your current code-behind) and the ascx.
Base Class (your refactored code-behind)
Public MustInherit Class BaseUC
Inherits System.Web.UI.UserControl
' Define all page controls here
Public MustOverride Property lblPageTitle As Label
Private Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
' Check if control has been initialized by inheriting class
If lblPageTitle IsNot Nothing Then
lblPageTitle.Text = "Hooray"
End If
End Sub
End Class
New site-specific code behind
Partial Class controls_Sample
Inherits BaseUC
Public Overrides Property lblPageTitle As System.Web.UI.WebControls.Label
Get
Return title
End Get
Set(value As System.Web.UI.WebControls.Label)
title = value
End Set
End Property
End Class
Site specific ASCX
<%# Control Language="VB" AutoEventWireup="false" CodeFile="Sample.ascx.vb" Inherits="controls_Sample" %>
<asp:Label runat="server" ID="title"></asp:Label>
I have created simple controls that are based on dot net controls . For example there is a simple GridView control that is based on dot net GridView control , I just set some setting in my control to use it in my .aspx pages , for example I set the Width of GridView in the constructor method :
// constructor of my custom class
public GridView(): base()
{
this.Width = new Unit(100, UnitType.Percentage);
}
and also I've added some custom properties :
public int SelectedID
{
get
{
if (ViewState["SelectedID" + this.ID] == null)
ViewState["SelectedID" + this.ID] = "-1";
return Convert.ToInt32(ViewState["SelectedID" + this.ID]);
}
set
{
ViewState["SelectedID" + this.ID] = value;
}
}
The *Problem* : when I use Tools>Generate Local Resource in VS2010
the aspx markup before I use this tool is like this :
<RPC:GridView ID="grdData" runat="server" onrowcommand="grdData_RowCommand">
but this tool adds any public property or any setting to my aspx markup , like this :
<RPC:GridView ID="grdData" runat="server" onrowcommand="grdData_RowCommand"
meta:resourcekey="grdDataResource1" SelectedID="-1" Width="100%">
I don't like VS2010 add my settings (like width) and my custom properties (like SelectedID) to aspx markup , this prevent me having the ability of changing my custom control code and reflect changes in all aspx pages that include this control , for example if
I change the width of my control to 50% , it doesn't reflect to any pages
Please tell me what should I do to fix my problem
Thank you very much for your feedbacks
This is a slightly complicated topic to address in one answer here to be honest! There are more than one approaches you can take to resolve this problem. It all depends on the kind of properties your control has and if it is a templated control or not. As a quick fix try decorating your public properties with the following attribute
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
or if you don't want the user to be able to set the public property at all via HTML markup then use
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
another attribute declaration which will be helpful with
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
is
PersistenceMode(PersistenceMode.Attribute)
I've found doing any initialisation in the ctor causes major headaches for local resource generation (even corruption). Use the DefaultValue attribute on properties and/or use OnLoad if possible. (As a side note use CSS rather than explicitly setting control width).
Im working with Sitefinity and I'm developing a Control Designer - however i dont think my question is specific to SiteFinity.
I have a class such as:
public class CaseStudyFeaturedItem : CaseStudySelectorControlDEsignerBase
The class it is inherriting from is itself inheriting from UserControl, like so:
public class CaseStudySelectorControlDesignerBase : System.Web.UI.UserControl {
Within CaseStudyFeaturedItem is it possible to load a template which is an embedded resource and then access the controls on that control?
So essentially, I have usercontrol.ascx which is an embedded resource so has a string like:
mynamespace.myclass.usercontrol.ascx;
And from within CaseStudyFeaturedItem I want to be able to load that usercontrol and then modify the controls (i.e. literals/labels) that are within it?
Is this possible?
Thanks
Al
We do this with every control in Sitefinity, but it would be a little complicated to do with your own custom controls (I assume you are using Sitefinity 3.7). The steps are the following:
- Implement a template container control, inheriting from GenericContainer:
protected class ItemListContainer : GenericContainer
{
public virtual Repeater RepeaterControl
{
get { return base.GetControl<Repeater>("repeater", true); }
}
}
- You need to get the template from the resource (use ControlUtils.GetTemplate method - Sitefinity does that for you):
public virtual ITemplate ItemListTemplate
{
get
{
if (itemListTemplate == null)
itemListTemplate = ControlUtils.GetTemplate(<virtual path to template>, <resource file name>,
<type to determine assembly for template>);
return itemListTemplate;
}
set
{
itemListTemplate = value;
}
}
- You need to call InstantiateIn method of the template, and pass it the container control
listContainer = new ItemListContainer();
ItemListTemplate.InstantiateIn(listContainer);
- Access all controls through the container
listContainer.RepeaterControl.DataBind();
In Sitefinity 4.0 we've included a base class for all controls, which will give you this functionality out of the box. In 3.7 though, you'll have to do all this by hand.
The ControlUtils class is in the Telerik.Framework.Web namespace. The code above is how this all is done in the ContentView control, you should probably make slight modifications depending on your case.
Cheers,
Slavo
The Sitefinity team # Telerik
Yes it is possible, but I'm not entirely sure what you're trying to accomplish based on your question. You can use LoadControl to dynamically load user controls. If you cast the result to the appropriate control type, you will then have access to all of its properties. From there, you can add it into whatever container you want to hold it. Is that the kind of thing you're trying to do?
If I put a control in a .aspx file like this;
<asp:TextBox ID="protectedTextBox" runat="server">Some info</asp:TextBox>
I get a declared control in the page's .aspx.designer.cs file;
protected global::System.Web.UI.WebControls.TextBox protectedTextBox;
But I'd like to change the access modifier of the control to public. Is there any attribute or similar that I can set to change the access modifier?
Here's why I want to do it. I am trying to have cross-page postbacks work nice and neatly. I have two pages:
FirstPage.aspx
MyTextBox : textbox
MyButton : button, #PostbackUrl=Secondpage
SecondPage.aspx
MyLabel : label
When the user clicks FirstPage.MyButton, I want to write the value of FirstPage.MyTextBox.Text into SecondPage.MyLabel.Text. I could do it with Page.FindControl, but this seems like a poor substitute to casting the previous page as a FirstPage object and referring directly to the MyTextBox control on it. Something like this;
// on the page_load of SecondPage.aspx;
var previousPage = this.PreviousPage as FirstPage;
this.MyLabel.Text = previousPage.MyTextBox.Text;
Is there any way to change the access modifier?
You can just delete the declaration from the designer and put it in your code behind.
The comments around the declaration say to do this.
/// To modify move field declaration from designer file to code-behind file.
One option I've considered is writing a public property which exposes the original page;
public TextBox PublicTextBox { get { return this.MyTextBox; } }
Which would get the job done, but seems hacky.
Steve, exposing that page's controls would make sense if you'd need to manipulate those controls, but in your case you just need to pass some data (that string) to the other handler, so I would expose that and not the control itself.
Summary
Hi All,
OK, further into my adventures with custom controls...
In summary, here is that I have learned of three main "classes" of custom controls. Please feel free to correct me if any of this is wrong!
UserControls - Which inherit from UserControl and are contained within an ASCX file. These are pretty limited in what they can do, but are a quick and light way to get some UI commonality with designer support.
Custom Composite Controls - These are controls that inherit from WebControl where you add pre-existing controls to the control within the CreateChildControls method. This provides great flexibility, but lack of designer support without additional coding. They are highly portable though since they can be compiled into a DLL.
Custom Rendered Controls - Similar to Custom Composite Controls, these are added to a Web Control Library project. The rendering of the control is completely controlled by the programmer by overriding the Render method.
My Thoughts..
OK, so while playing with custom composites, I found the following:
You have little/no control over the HTML output making it difficult to "debug".
The CreateChildControls (and subsequent methods) can get real busy with Controls.Add(myControl) everywhere.
I found rendering tables (be it for layout or content) to be considerably awkward.
The Question(s)..
So, I admit, I am new to this so I could be way off-base with some of my points noted above..
Do you use Composites?
Do you have any neat tricks to control the HTML output?
Do you just say "to hell with it" and go ahead and create a custom rendered control?
Its something I am keen to get really firm in my mind since I know how much good control development can cut overall development time.
I look forward to your answers ^_^
I say go ahead with the custom rendered control. I find that in most cases the composite can be easier done and used in a UserControl, but anything beyond that and you'd need to have a finer degree of control (pun unintended) to merit your own rendering strategy.
There maybe controls that are simple enough to merit a composite (e.g., a textbox combined with a javascript/dhtml based datepicker, for example) but beyond that one example, it looks like custom rendered controls are the way to go.
Here's another extension method that I use for custom rendering:
public static void WriteControls
(this HtmlTextWriter o, string format, params object[] args)
{
const string delimiter = "<2E01A260-BD39-47d0-8C5E-0DF814FDF9DC>";
var controls = new Dictionary<string,Control>();
for(int i =0; i < args.Length; ++i)
{
var c = args[i] as Control;
if (c==null) continue;
var guid = Guid.NewGuid().ToString();
controls[guid] = c;
args[i] = delimiter+guid+delimiter;
}
var _strings = string.Format(format, args)
.Split(new string[]{delimiter},
StringSplitOptions.None);
foreach(var s in _strings)
{
if (controls.ContainsKey(s))
controls[s].RenderControl(o);
else
o.Write(s);
}
}
Then, to render a custom composite in the RenderContents() method I write this:
protected override void RenderContents(HtmlTextWriter o)
{
o.WriteControls
(#"<table>
<tr>
<td>{0}</td>
<td>{1}</td>
</tr>
</table>"
,Text
,control1);
}
Rob, you are right. The approach I mentioned is kind of a hybrid. The advantage of having ascx files around is that on every project I've seen, designers would feel most comfortable with editing actual markup and with the ascx you and a designer can work separately. If you don't plan on actual CSS/markup/design changes on the controls themselves later, you can go with a custom rendered control. As I said, my approach is only relevant for more complicated scenarios (and these are probably where you need a designer :))
I often use composite controls. Instead of overriding Render or RenderContents, just assign each Control a CssClass and use stylesheets. For multiple Controls.Add, I use an extension method:
//Controls.Add(c1, c2, c3)
static void Add(this ControlCollection coll, params Control[] controls)
{ foreach(Control control in controls) coll.Add(control);
}
For quick and dirty rendering, I use something like this:
writer.Render(#"<table>
<tr><td>{0}</td></tr>
<tr>
<td>", Text);
control1.RenderControl(writer);
writer.Render("</td></tr></table>");
For initializing control properties, I use property initializer syntax:
childControl = new Control { ID="Foo"
, CssClass="class1"
, CausesValidation=true;
};
Using custom composite controls has a point in a situation where you have a large web application and want to reuse large chunks in many places. Then you would only add child controls of the ones you are developing instead of repeating yourself.
On a large project I've worked recently what we did is the following:
Every composite control has a container. Used as a wrapped for everything inside the control.
Every composite control has a template. An ascx file (without the <%Control%> directive) which only contains the markup for the template.
The container (being a control in itself) is initialized from the template.
The container exposes properties for all other controls in the template.
You only use this.Controls.Add([the_container]) in your composite control.
In fact you need a base class that would take care of initializing a container with the specified template and also throw exceptions when a control is not found in the template. Of course this is likely to be an overkill in a small application. If you don't have reused code and markup and only want to write simple controls, you're better off using User Controls.
You might be able to make use of this technique to make design-time easier:
http://aspadvice.com/blogs/ssmith/archive/2007/10/19/Render-User-Control-as-String-Template.aspx
Basically you create an instance of a user control at runtime using the LoadControl method, then hand it a statebag of some kind, then attach it to the control tree. So your composite control would actually function like more of a controller, and the .ascx file would be like a view.
This would save you the trouble of having to instantiate the entire control tree and style the control in C#!