Web User Controls
Last Updated: 2001
Note:
This section was written before I did any commercial work with Custom Controls.
I really must get round to rewriting the whole thing. I think I could write an
entire book on the subject now... :-)
User controls are basically normal .aspx web pages but with the extension
changed to .ascx to prevent people running them directly.
To use a User Control on a web form, it must first be registered in the using
page. This basically allows the definition of a namespace in which a particular
user control will run so that we can don't have to worry about two user control
class names being the same. Registration is (always??) performed at the top of
the file and takes the form:
<%@ Register TagPrefix="myNamespace"
TagName="myControlName" Src="somePage.ascx" %>
Once registered the control can be placed on the page thus:
<myControlName id="asUsual"
runat="server" />
User Controls can be loaded dynamically:
Control cntrlNew = LoadControl("myControl.ascx");
Page.Controls.Add(cntrlNew);
To refer a property of the loaded control it is obviously necessary to cast
it from the generic Control class to the correct class. The class name is
declared in the user control page (not the page that uses the control) and is
declared using the <@ Control ClassName = "somename"
%> directive (which replaces the @Page directive). Once this is done, the new object can be cast as usual
(e.g. ((somename) cntrlNew).SomeProp = "blah";)
Note: For casting to be available the control must have the
corresponding Register line in the containing form. Having the URL that would
have be included in the Register line in the LoadControl line is not sufficient.
Composite Controls
- Derived from System.Web.UI.Control or System.Web..WebControls.WebControl
- Derive from INamingContainer (this is a "marking" interface - it contains
no methods)
- Uses Controls.Add(control) to add child controls in protected
override void CreateChildControls()
- To force display in editor of child controls, override the Render
functions: protected override void Render(HtmlTextWriter op) {EnsureChildControls();
base.Render(op);}
Custom Controls
- Derived from System.Web.UI.Control or System.Web..WebControls.WebControl
- Have to live in an assembly. Circular references mean that that the test
assembly cannot be the same as the control assembly. Easiest solution: create
two projects in the Solution and add a reference from the control solution to
the test solution.
- Override the protected void Render(HtmlTextWriter output) method to output
whatever HTML you deem appropriate
- Text between tags can be accessed by looking at the Controls array for
items of type LiteralControl. To check to see if the Controls array actually
has anything in it, check HasControls(). E.g.
(HTML file)
....
<MyNamespace.MyControl runat=server>Some Inner Text</MyNamespace.MyControl>
....
(.cs file)
...
protected override void Render(HtmlTextWriter output)
if(HasControls() && (Controls[0] is LiteralControl)
output.write("<H1>Inner text was: " + ((LiteralControl) Controls[0].Text +
"</H1>");
...
- Override CreateChildControls to create instances of any required child
controls, for example:
...
protected override void CreateChildControls() {
this.Controls.Add(new TextBox());
....
}
- Controls have a State property (System.Web.UI.StateBag) to which property
and value pairs can be written. The framework writes these values to the
hidden field and ensures they survive the round trip. It is the coder's
responsibility to read the pairs back. For example:
...
public string myProp { get {return (string) State["myProp"];}
set {State["myProp"] = value;} }
...
?? I think we should be using ViewState by preference, i.e.:
...
public int myProp { get {return (int) ViewState["myProp"];}
set {State["myProp"] = value;}}
...
Note that properties altered on the server that are not then re-saved to the
StateBag will return to their previous values when the page next loads. For
example, I had a calendar that I could advance one month forward or one month
back from the start date only. This was because the page rendered with the
modified values but the next postback had the previous, default, value.
- ?? State can be automated by implementing the IPostBackDataHander
interface. For example:
private int _myProp;
....
public bool LoadPostData(string postDataKey,
NameValueCollection values) {
_myProp =
Int32.Parse(values[this.UniqueID]);
return (false); //
Required to prevent RaisePostDataChangedEvent from firing
}
public void RaisePostDataChangedEvent() {} // ?? Required to
fulfil interface definition ??
?? how on earth does it know what properties to save ??
?? something about it only working for non-composite controls ??
- ?? Templates ??
- To trigger a postback event in the rendered control (e.g. from the month
combo of a client side control):
- Add IPostBackEventHandler the the inheritance list of the class
- Implement the public void
RaisePostBackEvent(string strEvent) function (which is all the
IPostBackEventHandler interface requires). This function will be called on
postback for this control with strEvent set to a user specified value (see
below). A control can have as many values for strEvent (i.e. as many
different "events" as it likes).
- Call Page.GetPostBackEventReference(this, "strEvent") to
- Automatically add the following code to the page:
function __doPostBack(eventTarget,
eventArgument) {
var theform = document.formID;
theform.__EVENTTARGET.value
= eventTarget;
theform.__EVENTARGUMENT.value =
eventArgument;
theform.submit();
}
- Return the code required to call this function as a string.
Currently the returned string looks like:
__doPostBack('controlName', 'strEvent')
For example, if we were rendering a button:
... [render code]
oP.Write("<input type=button value='Do a post back" onclick=\""
+
Page.GetPostBackEventReference(this,
'myEvent') + "\" >");
... [render code]
or a label:
... [render code]
oP.Write("<a href="\javascript:" +
Page.GetPostBackEventReference(this, 'myEvent') +
"\" >Do a
post back</a>");
... [render code]
Note that it is currently impossible to "break out" of strEvent to
include additional information, such as a value (i.e. P.GPBER(this, 'myEvent\"+this.value+\"'
or similar doesn't work - the function automatically adds the required "\"
to make the embedded quotes remain embedded).
What I've done with the calendar control (where I want all the combos to be
client side for all the usual reasons) to get round this is to buffer the
result of Page.GetPostBackEventReference. For example:
string strCallBack =
Page.GetPostBackEventReference(this, "%XYZ%")
...[render code]
oP.Write("<SELECT id=cboMonth.... onchange='" +
strCallBack.Replace("%XYZ%", "monthChange:\"" + this.value +
"\"") + ...
...[render code]
I then extract the number after the colon in the RaisePostBackEvent
function.
It goes without saying that this approach is not necessarily future
compatible, however I can find no other way (except for adding client side
javascript to implement massive case statements) to resolve this.