I am trying to create a server control with child controls that are exclusively derived from a particular class. The child controls in this case are all custom server controls also. This particular web application uses a Skin file to apply styles to these child controls.
Previously, these child controls were only used as child controls of another server control that allowed any markup. The skin works fine, though there are some deficiencies with this model.
The old model -- which I must still support for legacy compatibility reasons -- looked something like
<myns:FieldPanel ID="SomeId" runat="server" LabelText="SomeLabelText">
<myns:SomeFieldControl ID="SomeControlName" runat="server" SomeProperty="false" />
<myns:SomeOtherFieldControl ID="AnotherControl" runat="server" SomeProperty="false">
<Items>
<asp:ListItem Text="One" Value="1" />
<asp:ListItem Text="Dos" Value="2" />
<asp:ListItem Text="Trois" Value="3" />
</Items>
</myns:SomeOtherFieldControl>
</myns:FieldPanel>
The FieldPanel
control is defined like
[ParseChildren(false)]
public class FieldPanel : WebControl
{
public override void RenderBeginTag(HtmlTextWriter writer)
{
// some rendering
}
public override void RenderEndTag(HtmlTextWriter writer)
{
// some more rendering
}
public string PanelLabelCssClass { get; set; }
public string LabelText { get; set; }
}
Both SomeFieldControl
and SomeOtherFieldControl
inherit from an abstract base class called Field
.
Similar markup that would use my new server control would be
<myns:FieldTablePanel ID="AnotherId" runat="server" LabelText="OtherLabelText">
<Fields>
<myns:SomeFieldControl ID="SomeControlName" runat="server" SomeProperty="false" />
<myns:SomeOtherFieldControl ID="AnotherControl" runat="server" SomeProperty="false">
<Items>
<asp:ListItem Text="One" Value="1" />
<asp:ListItem Text="Dos" Value="2" />
<asp:ListItem Text="Trois" Value="3" />
</Items>
</myns:SomeOtherFieldControl>
</Fields>
</myns:FieldTablePanel>
FieldTablePanel
has a lot more code than FieldPanel
, but some basics:
[DefaultProperty(null)]
[ParseChildren(true)]
public class FieldTablePanel : FieldPanel, INamingContainer
{
private FieldCollection _fields;
private Table _innerTable;
public class FieldCollection : Collection<Field>, INamingContainer
{
// mainly standard Collection methods that I have to do something special with
}
[Bindable(false)]
[DefaultValue(null)]
[TemplateContainer(typeof(FieldCollection))]
[PersistenceMode(PersistenceMode.InnerProperty)]
// Note that FieldCollection does not work here
public Collection开发者_JAVA技巧<Field> Fields
{
get
{
return _fields;
}
set
{
_innerTable.Rows.Clear();
_fields = new FieldCollection(_innerTable, Page, value);
}
}
}
I left out a lot of methods here, of course. I have everything rendering the way I want except that the skins are not applied to SomeFieldControl
and SomeOtherFieldControl
as they should be. Further, when running the code, both Parent
and Page
for SomeFieldControl
and SomeOtherFieldControl
are null
. This is not the case in the legacy example.
I would like to avoid just using ITemplate
for the collection of inner controls unless there is no other way, since I am relying on all the child controls being derived from Field
. What am I missing here? I can provide more code on request.
Edit:
The skin file (Site.skin, in this case) has an entry for each of the custom Field
controls as well as FieldPanel
and FieldTablePanel
. The relevant portions:
<myns:FieldPanel runat="server"
PanelLabelCssClass="formPanelLabel"
Width="100%"
/>
<myns:FieldTablePanel runat="server"
PanelLabelCssClass="formPanelLabel"
Width="100%"
/>
<myns:SomeFieldControl runat="server"
LabelCssClass="formFieldLabel"
ResourceClassKey="CommonResource"
RequiredErrorMessageResourceKey="RequiredMessageFormat"
/>
<myns:SomeOtherFieldControl runat="server"
LabelCssClass="formFieldLabel"
RequiredErrorMessageResourceKey="RequiredMessageFormat"
ResourceClassKey="CommonResource"
ChooseOneResourceKey="ChooseOneOption"
NoneResourceKey="NoneOption"
/>
I did make a slight edit to the definition of FieldPanel
above to include the correct property name PanelLabelCssClass
, which is a CSS class that gets applied to a particular label. The Field
base class has a property called LabelCssClass
which, again, applies a CSS class to a label within the control (which works when the control is within FieldPanel
but not when the control is within the <Fields>
section of FieldTablePanel
).
The relevant CSS:
.formFieldLabel
{
font-family: Tahoma, Verdana, Arial, Helvetica, san;
font-size: smaller;
}
.formPanelLabel
{
font-family: Tahoma, Verdana, Arial, Helvetica, san;
color: #666666;
font-size: x-small;
}
Another Edit:
To my FieldTablePanel
class definition, I got rid of my custom override of Controls
(which just basically returned the field collection) and added:
protected override void OnInit(EventArgs e)
{
if (_fields != null)
{
_fields.ToList<Field>().ForEach(this.Controls.Add);
}
}
No luck. The skins are still not being applied to the Field
controls.
The reason I initially had an override on Controls
was to try to ensure that getting Controls
would return a ControlCollection
that was in sync with Fields
at all times. I added similar code to the setter in my Fields
property (specifically, adding this.Controls.Clear();
and _fields.ToList().ForEach(this.Controls.Add);
at appropriate places) with no change in results. I could try manipulating the FieldCollection
to ensure that when a
Field
is added that it is added to the Control
collection, but I don't know whether that would help.
One more thing I did try was adding my inner Table
as a Control of FieldTablePanel, but doing so just gave me the error
Multiple controls with the same ID '...' were found. Trace requires that controls have unique IDs.when going to the page with the control.
精彩评论