Breid uw Mogelijkheden uit met Extender Providers
Introductie
In dit artikel bekijken we hoe we van een Extender provider kunnen overerven wordt en hoe deze dan toegepast wordt in een WinForm applicatie.
Probleemstelling: een alternatieve manier van vertalen
In ons huidige project kreeg ik te maken met een heel specifiek probleem. Wij ontwikkelen een commerciële WinForms applicatie welke internationaal verkocht gaat worden. Hoewel de applicatie al Engelstalig is, moeten ook andere talen ondersteund worden. Natuurlijk zijn resource files en resource strings dan de eerste keuze. Maar voor de vorige (C++) versie van de applicatie zijn alle in de applicatie aanwezige teksten al eens vertaald in de te ondersteunen talen, dus is besloten om voort te borduren op deze vertalingen. De vertaalde teksten worden meegeleverd bij de applicatie in een ‘database’ (lees: een dll met één enkele functie om n.a.v. een integer tekstcode en een taalcode een tekst-string te achterhalen).
Hoe moet iedere control aan een tekstcode gekoppeld worden en hoe moet de vertaalde tekst dan in de Text property getoond worden?
Het is dan de kunst om bij constructie van schermen ook even langs alle visuele controls te lopen om zo de Text properties te vervangen door vertaalde teksten. Zie daar het probleem: hoe moet iedere control aan een tekstcode gekoppeld worden en hoe moet de vertaalde tekst dan in de Text property getoond worden?
Er is een aantal mogelijkheden:
- Een uitgecodeerde mapping? Dat kan wellicht handiger…
- De Tag property. Iedere control heeft toch een tag property? ‘Loop’ dan na de WinForm constructie recursief door alle controls heen en vervang de Text property met behulp van de tekstcode in de tag.
Als het even kan moet het gebruik van de Tag property ontmoedigd worden. De tag accepteert namelijk alles (wat afgeleid is van Object) en is dus niet bepaald strikt.
- Overerf ieder te gebruiken soort control en vul de tekst bij constructie van het control. Nogal omslachtig, niet?
- Extender providers …
De kans is groot dat je al eens gebruik hebt gemaakt van een dergelijke Extender Provider. Heb je al eens de Tooltip of de Error provider toegepast? Dan moet het ook opgevallen zijn dat een willekeurige control op het WinForm, bij het toevoegen van zo’n provider, spontaan extra properties heeft gekregen: b.v. “Error on ErrorProvider1” of “ToolTip on ToolTip1”.
Deze properties zijn van een bepaald type en dus wordt dat type strikt afgedrongen, in tegenstelling tot de tag property.
IExtenderProvider interface
Maar hoe wordt dit nu gedaan? Om de werking te verduidelijken ga ik een voorbeeldcomponent behandelen om bovenstaande probleemstelling op te lossen met een Extender Provider. We bouwen dus een TranslationExtender (zie figuur 1).

Fig. 1: De Extender Provider in de toolbox
We beginnen bij de basis: we leiden een nieuwe component af van Component. Deze moet tevens een bepaalde interface ondersteunen: IExtenderProvider.
Public partial class TranslationExtender :
Component, IExtenderProvider
{
… // constructors
#region IExtenderProvider Members
bool IExtenderProvider.CanExtend(object extendee)
{
return (extendee is Control);
}
#endregion
}
Met deze methode kan afgedwongen worden welke soorten objecten op het WinForm straks bij het toevoegen van deze TranslationExtender ondersteund worden door de Extender Provider. Nu ondersteunen we alle Controls.
Is dit alles? Ja en Nee
ProvideProperty attribute
Is dit alles? Ja en Nee. Dit is inderdaad de interface-implementatie, maar de properties om aan andere controls te hangen worden via een ‘Attribuut’ bekend gemaakt. Zet dan eerst boven de klassedefinitie van onze provider het ProvideProperty attribuut. Vervolgens moeten twee methodes toegevoegd worden met bijpassende naamgevingen. Bij de “property” TranslationId verschijnen dan GetTranslationId en SetTranslationId:
ProvideProperty("TranslationId", typeof(Control))]
public partial class TranslationExtender :
Component, IExtenderProvider
{
…
public int GetTranslationId(Control control)
{
}
public void SetTranslationId(
Control control, int value)
{
}
Let op: Er is dus geen echte C#-property toegevoegd met get{} en set{} definitie, alleen twee publieke methodes met een voorgeschreven naamgeving!
Merk op dat een Control als input wordt gebruikt. Straks zal bij alle Controls de extra property ‘TranslationId’ verschijnen waar de gebruiker een (integer) waarde gaat intypen. In de InitializeComponent van het mainform wordt dan voor iedere control een extra regel toegevoegd: b.v. this.extenderComponent1.SetTranslationId(this.button2, 2). Onze afgeleide component wordt aangeroepen en krijgt de opdracht om iets met de waarde 2 voor control Button2 te doen.
Er is ook een Get methode waarmee dan nog wat later de waarde voor een control (b.v. Button2) kan worden opgevraagd. Hier zou dan eindelijk de vertaling kunnen plaats vinden.
Laten we dus eerst maar die door de ontwikkelaar ingegeven waarden tijdelijk in het geheugen opslaan d.m.v. een Dictionary:
public partial class TranslationExtender :
Component, IExtenderProvider
{
private const int INVALID_ID = -1;
private Dictionary _dictionary =
new Dictionary();
// constructors
public int GetTranslationId(Control control)
{
if (control == null)
{
return INVALID_ID;
}
int value = INVALID_ID;
if (_dictionary.TryGetValue(
control.GetHashCode(), out value))
{
return value;
}
else
{
return INVALID_ID;
}
}
public void SetTranslationId(
Control control, int value)
{
if (control == null)
{
return;
}
_dictionary.Remove(control.GetHashCode());
_dictionary.Add(control.GetHashCode(), value);
}
De waarden voor alle gewenste controls zijn nu opgeslagen in de Dictionary, vlak na de InitializeComponent. Dat ziet er dan zo uit (zie figuur 2):

#BMP Velde_ExtenderProviders_02.bmp
Fig. 2: De property TranslationId in iedere control
Dus nu kunnen alle Text properties van de op het form aanwezige controls vervangen worden. Toch kan dit nog iets fraaier!
In plaats van een recursieve slag kan ook het event handling mechanisme gebruikt worden
Slim gebruik van event handler
In plaats van het vertalen van ieder control door middel van een recursieve slag kan ook het event handling mechanisme gebruikt worden. We zoeken dan eerst naar een event van de control welke afgaat nadat een control geïnitialiseerd is. Als deze afgaat, dus na de constructie van een control, wordt de vertaling voor die control opgehaald. Dit is b.v. het HandleCreated event:
public void SetTranslationId(Control control, int value)
{
if (control == null)
{
return;
}
_dictionary.Remove(control.GetHashCode());
_dictionary.Add(control.GetHashCode(), value);
if (!DesignMode)
{
control.HandleCreated +=
new EventHandler(control_HandleCreatedHandler);
}
}
private void control_HandleCreatedHandler(
object sender, EventArgs e)
{
int id = this.GetTranslationId((Control)sender);
switch (id)
{
case 1:
((Control)sender).Text = "Het ene control";
break;
case 2:
((Control)sender).Text = "De tweede";
break;
case 3:
((Control)sender).Text = "En de derde";
break;
}
}
Zodra het Winform geconstrueerd wordt, zal voor iedere control een gedelegeerde control_HandleCreatedHandler af gaan. Hierin wordt de daadwerkelijke (en in dit geval eevoudige) vertaling gedaan. Er hoeft dus geen extra recursieve slag uitgevoerd te worden.
Conclusie
Daar waar de tag lonkt kan de Extender Provider een veel beter alternatief zijn. De Extender Provider heeft hier laten zien dat met een minimale inspanning een heel flexibele oplossing voor het vertalingsprobleem gebouwd kan worden.
Overigens kan een Extender Provider meerdere properties naast elkaar ondersteunen en het is zelfs mogelijk om properties aan speciale groepen van objecten toe te wijzen.
Breidt dus voortaan uw mogelijkheden uit met Extender Providers.