The Vision Column - Custom controls in ASP.NET: de TabControl (deel 1)
The Vision Column wordt verzorgd door ontwikkelaars van
The Vision Web (tegenwoordig Ordina).
Door Arnold Jan van der Burg
5 oktober 2007
Iedere ontwikkelaar in ASP.NET kent de standaard ASP.NET webserver controls wel. Meestal is deze
set van controls voldoende om de functionaliteit te bouwen die je voor ogen hebt. Soms kan het
echter zo zijn dat deze tekort schieten of niet precies doen wat je als ontwikkelaar zou willen,
of het kan zijn dat je een bepaald type control nodig hebt dat eenvoudigweg niet beschikbaar is.
Gelukkig is het in ASP.NET niet heel ingewikkeld om een custom control te bouwen, of toch wel?
User controls vs. custom controls
Binnen ASP.NET zijn er twee typen "custom" control. Je hebt het UserControl type en het Custom
WebControl type. Het verschil tussen deze twee vormen staat in de volgende tabel:
|
User control |
Custom control |
| Visuele ondersteuning bij aanmaken |
+ |
- |
| Scheiding code en HTML |
+ |
- |
| Precompiled code |
- |
+ |
| Visuele ondersteuning bij toevoegen in pagina |
- |
+ |
| Herbruikbaarheid |
- |
+ |
User controls zijn gemakkelijk als je snel iets specifieks voor één applicatie wilt maken, custom
controls zijn echter weer makkelijker te delen over meerdere applicaties, omdat ze gecompileerd
worden naar een DLL. Vooral als je dynamische content moet verwerken in je control, kun je beter
een custom control gebruiken.
TabControl
Ik heb voor een klein project binnen T-Mobile een Tab control gebouwd voor ASP.NET. Deze control
moest aan de volgende eisen voldoen:
- Het aantal tabs kan variëren.
- Een tab kan andere ASP.NET controls en HTML bevatten.
- ASP.NET controls binnen een tab zijn in de pagina beschikbaar als schijnbaar losse controls.
- Event afhandeling voor het wisselen van tab (zowel server-side als client-side). De control moet kunnen onthouden welke tab geselecteerd is.
- Het moet mogelijk zijn om tabs uit te schakelen of onzichtbaar te maken.
- Validatie controls moeten per tab kunnen worden in- of uitgeschakeld worden, om te voorkomen dat deze per ongeluk worden afgevuurd.
- Visueel onderscheid tussen de actieve en inactieve tabs (voor- en achtergrond kleuren).
- De tab moet zowel horizontaal als verticaal kunnen worden getoond.
- Gebruik van additionele CSS styles moet worden ondersteund.
Uit bovenstaande eisen kan logischerwijs worden afgeleid dat het hier een custom control betreft.
Het is uiteindelijk een redelijk complex control geworden, waarvan ik de werking in deze en komende
column zal uitleggen. Daarnaast ik zal proberen een schets te geven van de mogelijkheden die
je hebt bij het bouwen van een custom ASP.NET control.
De basis
In feite wordt mijn tab control niets anders dan een HTML tabel, waarbij de bovenste regel cellen
de tab headers vormen. De rijen cellen daaronder vormen de inhoud van de tabs. Door slim gebruik
te maken van styles wordt een 3D effect gesuggereerd (zie Afbeelding 1).
Afbeelding 1
Telkens als een tab wordt geselecteerd, zal de bijbehorende tabel rij (met daarin de controls van
de geselecteerde tab) getoond worden. Om ervoor te zorgen dat de juiste controls bij de juiste tab
getoond worden en dit te kunnen onthouden in de control heb ik de volgende oplossing bedacht:
1. Er bestaat een collectie van tabs. Iedere tab vormt weer de parent control voor de controls binnen de tab zelf.
2. Iedere control binnen een tab moeten worden gepersisteerd naar de pagina. Op deze manier is iedere control binnen een tab los bereikbaar.
De basis van de tab control wordt dus gevormd door de collectie van tabs. Deze TabCollection class
is eigenlijk niets meer dan een proxy class die de tabs zal toevoegen aan de Controls collectie
van de TabControl class zelf. Op deze manier hoeft de control niet zelf een aparte collectie te
bevatten (zie Afbeelding 2).

Afbeelding 2
Om ervoor te zorgen dat alleen tabs kunnen worden toegevoegd aan deze collectie, zal dit een custom
ControlCollection class zijn. Op deze manier kan worden afgevangen of de toegevoegde control wel
daadwerkelijk een control van het type Tab is. Zoals in de eisen vermeldt staat, moeten de controls
binnen een tab zichtbaar zijn in de code behind. Dit is in te stellen door gebruik te maken van
zogenaamde "class attributes".
Class attributes
Een class attribute is een handige een manier om metadata over een class op te slaan. Een aantal
ervan zijn bijzonder handig als het aankomt op het bouwen van een custom control. De belangrijkste
attribute classes voor het bouwen van een custom control zijn de volgende:
- ParseChildrenAttribute class. Dit attribute wordt gebruikt om aan te geven dat XML elementen, die binnen het server control gedeclareerd zijn, moeten worden behandeld als eigenschappen of childcontrols van die control.
- PersistChildrenAttribute class. Dit attribuut geeft aan of de child controls moeten worden gedeclareerd als geneste controls. Dit houdt in dat de controls ieder apart zullen worden gedeclareerd als protected members van de pagina.
- DesignerAttribute class. Met dit attribuut kan worden verwezen naar de designer class die de custom control in design-time modus laat zien (meer over designers in deel 2 van deze column).
- PersistenceModeAttribute class. Dit attribuut wordt toegepast op properties van het control en bepaalt hoe de eigenschappen van de control worden gepersisteerd in de pagina.
- DesignerSerializationVisibilityAttribute class. Dit attribuut wordt vaak in combinatie met het PersistenceMode attribuut gebruikt en bepaalt hoe de inhoud van een property moet worden geserialiseerd.
Het gaat hier te ver om de attributen uitgebreid te behandelen. Je kunt de MSDN library raadplegen
voor een uitgebreid overzicht van deze attributen.
Zoals eerder aangegeven moeten de controls van een tab zichtbaar zijn in de code behind. Dit
betekent dat bij het parsen van de tabcontrol, de controls binnen een tab moeten niet moeten
worden geïnterpreteerd. Hierdoor kan een tab zelf alle mogelijke controls bevatten. Om dit te
regelen, kan gebruik worden gemaakt van de bovengenoemde class attributes. Ter illustratie
hieronder de Tab class:
1: [ToolboxItem(false),
2:
3: ParseChildren(false),
4: PersistChildren(true),
5: DefaultProperty("Text"),
6: Serializable()]
7: public class Tab : Control, INamingContainer
Door het ParseChildren attribuut op false te zetten, zal de parser de inhoud van de tab negeren en
interpreteren als child controls van deze tab. Door het PersistChildren attribuut op true te zetten,
zullen deze controls worden toegevoegd aan de pagina class als protected members.
Events en postback
Om ervoor te zorgen dat de juiste tab getoond wordt, zal het tab control een property SelectedIndex
hebben. Met deze property wordt onthouden welke tab uit de collectie op dat moment geselecteerd is.
De eindgebruiker kan een andere tab aanklikken en daardoor de waarde van deze property wijzigen.
Om dit mogelijk te maken zal de control een extra event bevatten: OnSelectedIndexChanged. In dit
event wordt de geselecteerde tab gewijzigd en kan de inhoud van de nieuw geselecteerde tab worden
getoond. Om de control te laten reageren op het wijzigen van een de geselecteerde tab wordt gebruik
gemaakt van de IPostBackDataHandler en IPostBackEventHandler interfaces. Deze interfaces brengen de
control ervan op de hoogte dat er een postback naar de server heeft plaatsgevonden.
1: void IPostBackDataHandler.RaisePostDataChangedEvent()
2: {
3: // Stub Implementation, Required for IPostBackDataHandler
4: //though it doesn't use its methods.
5: this.OnSelectedIndexChanged(EventArgs.Empty);
6: }
7:
8: bool IPostBackDataHandler.LoadPostData(string postDataKey,
9: System.Collections.Specialized.NameValueCollection postDataCollection)
10: {
11: return true;
12: }
13: void IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
14: {
15: if (eventArgument == null)
16: return;
17:
18: this._selectedIndex = Int32.Parse(eventArgument);
19:
20: EventArgs e = new EventArgs();
21: this.OnSelectedIndexChanged(e);
22: }
Om de veranderde index op te slaan na een postback moeten wel de LoadViewState() en SaveViewState()
methodes aangepast worden om de gewijzigde index op te slaan. Om de dingen gegroepeerd op te slaan,
maak ik hier gebruik van een Triplet, een class die exact drie dingen op kan slaan. Op deze manier
is het gemakkelijk drie waardes weer uit de viewstate te kunnen halen.
1: protected override void LoadViewState(object viewState)
2: {
3:
4: Triplet currentState;
5: if (viewState != null && this.EnableViewState)
6: {
7: currentState = (Triplet) viewState;
8: this._selectedIndex = (int) currentState.First;
9:
10: }
11: else
12: {
13: base.SaveViewState();
14: }
15: }
16:
17: protected override object SaveViewState()
18: {
19: Triplet currentState;
20:
21: currentState = new Triplet();
22:
23: if (this.EnableViewState)
24: {
25: currentState.First = this.SelectedIndex;
26:
27: return currentState;
28: }
29: else
30: {
31: return base.SaveViewState();
32: }
33: }
Tot zover het eerste deel van de TabControl. In deel 2 zal ik ingaan op het renderen van de
control en design-time ondersteuning.
Referenties:
MSDN:
Recommendations for Web User Controls vs. Web Custom Controls:
http://msdn2.microsoft.com/en-us/library/aa651710(vs.71).aspx
Maintaining State in a Control
http://msdn2.microsoft.com/en-us/library/aa720269(vs.71).aspx
Processing Postback Data
http://msdn2.microsoft.com/en-us/library/aa720471(vs.71).aspx
|