Eigenschappen van een Verzameling
Introductie
Soms valt je opeens iets op terwijl het gewoon binnen handbereik ligt, zo ook met Visual Studio 2005. Al de hele tijd gebruiken we een nieuwe eigenschap van controls genaamd Anchor, terwijl deze eigenlijk vrij uniek is ten opzichte van alle andere eigenschappen. Kijk alleen maar eens naar de editor (figuur 1) in het Properties window. Hebt u die ook ergens anders in de IDE gezien?

Fig. 1:De Anchor eigenschap heeft een unieke editor
De anchor eigenschap is overigens ontzettend handig: een child control kan binnen een ‘container’ met de randen verankerd worden zodat het herschalen van de container ook de vorm van het child control dynamisch aanpast. We kunnen opgeven: Left, Top, Right, Bottom of juist None, maar ook een willekeurige combinatie van de eerste vier is mogelijk. De instelling gedraagt zich dus als een soort van verzameling van individuele keuzes uit een lijst van vooraf bekende mogelijkheden.
Voor de lezers met een Borland Delphi achtergrond komt dit vaag bekend voor. In die taal is het mogelijk een verzameling van enumeratie constanten samen te stellen (Set Of). De Anchor property blijkt op dezelfde manier te werken!
In dit artikel duiken we wat dieper in op een dergelijke eigenschap die een verzameling van keuzes representeert en we zullen ook zelf een editor voor de eigenschap creëren.
Het FlagsAttribute attribuut
Basis voor de verzameling met te selecteren waarden is een normale enumeratie-constanten-lijst, maar met een paar opvallende aanpassingen:
[FlagsAttribute]
public enum PropSet
{
None = 0,
Een = 1,
Twee = 2,
Drie = 4,
Vier = 8,
}
Ten eerste staat boven de enum een attribuut: FlagsAttribute, ten teken dat meerdere waarden samen geselecteerd kunnen worden (bitwise combination). Ook hebben alle elementen een integer waarde gelijk aan 2n . De geselecteerde enumeratie constanten moeten straks gezamenlijk in één waarde onthouden worden. Door de verschillende constanten gelijk te houden aan unieke 2n waarden, geeft iedere combinatie ook een unieke sommatie als eindgetal.
Als laatste ziet u dat er een extra veld ‘None’ met de waarde nul is opgenomen. Deze representeert de situatie dat er geen enkele element gekozen is… De reeks 0, 1, 2, 4, 8, enz. is het handelsmerk van een enum verzameling.
Een enumeratieverzameling property zelf is recht-toe-recht-aan:
private PropSet myVar;
public PropSet MyProperty
{
get { return myVar; }
set { myVar = value; }
}
In eerste instantie is dit alles wat betreft de eigenschap. De eigenschap accepteert nu iedere deelverzameling van de enum elementen. Kijk maar eens naar de nu al beschikbare property editor (figuur 2):

Fig. 2: De standaard 'editor' voor een verzameling
Via de dropdownlist kan helaas maar één waarde gekozen worden, maar in het tekstveld zelf kan iedere combinatie van elementen ingevoerd worden (gescheiden door komma’s) bv. een, twee. Dan wordt de combinatie van Een, Twee gerepresenteerd.
Vul in dit veld nu ook eens een waarde in tussen de nul en vijftien. Bij de waarde zes worden Twee en Drie gekozen. Met acht erbij, Veertien, wordt ook Vier gekozen. De eigenschap vertegenwoordigt dus eigenlijk een integer getal, samengesteld uit de som van de afzonderlijke enumeratie constanten!
Vergelijk dit nu eens met de Anchor property uit figuur 1).
Ook hier kan als alternatieve invoer een integer waarde ingevoerd worden. De waarde Twaalf resulteert in de combinatie van Left en Right.
Maar de dropdownlist van de Anchor eigenschap ziet er toch opvallend anders uit! Er kan bij deze alternatieve invoer nu wel een combinatie gemaakt worden van meerdere elementen. Voordat we ingaan op de vraag of wij ook zo’n property editor kunnen construeren, gaan we eerst eens met de ingevoerde waarden spelen.
Kies bij ons voorbeeld MyProperty eens de waarden Een en Drie. De eigenschap heeft dus de waarde integer waarde vijf (= 1 + 4):
MessageBox.Show(customControl11.MyProperty.ToString());
De ToString() geeft netjes “Een, Drie” terug. Maar hoe achterhaal ik nu de werkelijk geselecteerde elementen?
Validatie van een verzameling van enumeratie constanten
Uitgangspunt bij het achterhalen van de geselecteerde elementen is dat de elementen bits-gewijs bij elkaar ‘opgeteld’ worden, en wel met behulp van binaire operators: AND, OR en Exclusieve OR (XOR).
Bij hetzelfde voorbeeld (waarde 5) geeft
(customControl11.MyProperty ==
(PropSet.Een | PropSet.Twee | PropSet.Drie) )
// False
de waarde False terug. De gekozen verzameling bestaan dus niet uit Een, Twee en Drie.
Maar de vergelijking:
((customControl11.MyProperty & PropSet.Een) ==
(PropSet.Een))
// True
geeft wel True terug. Constante Een zit dus in de gekozen verzameling.
Kijk ook eens naar de volgende situaties:
((customControl11.MyProperty & PropSet.Twee)
== (PropSet.Twee)) // False
((customControl11.MyProperty & PropSet.Drie) ==
(PropSet.Drie)) // True
((customControl11.MyProperty & PropSet.Vier) ==
(PropSet.Vier)) // False
Dit kan ook in een static functie gevat worden:
public class PropertySetHelper
{
public static bool InEnumSet(
PropSet set, PropSet valuesSubSet)
{
bool result =
((set & valuesSubSet) == valuesSubSet);
return result;
}
}
De notatie is dan:
(PropertySetHelper.InEnumSet(
customControl11.MyProperty, PropSet.Drie)) // True
U had zeker al gezien dat InEnumSet een attribuut ‘set’ en een attribuut ‘valuesSubSet’ bevat. ValuesSubSet is meervoud, dus er kan van de gekozen verzameling ook getest worden op een deelverzameling met meerdere elementen.
We voegen nu Vier toe aan de te testen verzameling. Deze bevat nu dus Een, Drie en Vier. Om te testen of de geselecteerde verzameling oa. Drie en Vier bevat, kan de volgende notatie gebruikt worden:
(PropertySetHelper.InEnumSet(
customControl11.MyProperty,
PropSet.Drie | PropSet.Vier))
// true
Het resultaat is True.
Helaas werkt dit ook correct voor een lege verzameling… want iedere verzameling bevat altijd een lege verzameling, dus deze functie geeft bij het testen op een lege verzameling dus ALTIJD een true terug (zie ook http://nl.wikipedia.org/wiki/Lege_verzameling).
Dus testen op een lege verzameling moet dus gewoon met de volgende vergelijking:
(customControl11.MyProperty == PropSet.None)
UITypeEditor voor design-time property editors
We hebben dus gezien dat een verzameling van enum elementen zich gezamenlijk als een getal laat representeren, maar dat het ook als een echte verzameling via binaire operatoren te manipuleren is. In de IDE designer hebben we hier wat minder aan. We hadden al gezien dat de standaard DropDownList editor slechts één element accepteert en we willen juist een gave editor zoals bij Anchors.
Dit is mogelijk en is zelfs redelijk triviaal! We gaan nu zelf een alternatieve Property Editor koppelen en toepassen.
Overigens heeft Microsoft zelf niet altijd de moeite genomen om fraaie editors te ontwerpen. Zo heeft de eigenschap NotifyFilter van de veel gebruikte FileSystemWatcher geen eigen editor maar het invoeren van een getal geeft hetzelfde resultaat. Ook komt None niet voor in het rijtje enum waarden. Wellicht is het niet relevant om te luisteren naar… niks.
Als uitgangspunt voor onze eigen editor nemen we een eenvoudige NumericUpDown. Deze kan een getal smakelijk aanbieden met van die pijltjes erbij. Dit geeft de meest eenvoudige oplossing voor ‘multiselect’ (zie figuur 3 voor het gewenste resultaat)

Fig. 3: Onze property met een nieuwe editor
Er resten ons nu twee stappen: eerst het gecombineerd bouwen en registreren van de aan te roepen editor en daarna het koppelen van de specifieke editor aan onze eigen property.
Stap 1: Bouw en registratie van onze eigen editor
Editors in de Properties Windows zijn afgeleid van UiTypeEditor. We gaan hier dus ook onze eigen editor van afgeleiden:
using System;
using System.Drawing.Design;
using System.ComponentModel;
using System.Windows.Forms.Design;
using System.Windows.Forms;
namespace TestPropertySet
{
class MultiSetMembersEditor : UITypeEditor
{
…
}
}
Een afgeleide van UiTypeEditor moet twee methoden implementeren. De eenvoudigste is GetEditStyle. Vertel hier de IDE welke type editor getoond gaat worden:
public override UITypeEditorEditStyle
GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
Omdat we een dropdownlist willen gaan tonen retourneren we UITypeEditorEditStyle.DropDown. Als we voor Modal gekozen hadden, zou in het property window de drie puntjes (ellipsis (…)) getoond worden. Een modaal scherm zou dan die keuze moeten ondersteunen…
De tweede methode is iets lastiger maar niet onoverkomelijk. Deze methode wordt uitgevoerd als de ontwikkelaar de dropdown benadert. Deze methode heet EditValue.
public override object EditValue(
ITypeDescriptorContext context,
IServiceProvider provider,
object value)
{
SetMember result = SetMember.None;
if (context != null
&& context.Instance != null
&& provider != null)
{
IWindowsFormsEditorService editorService =
(IWindowsFormsEditorService)provider.
GetService(typeof(IWindowsFormsEditorService));
if (editorService != null)
{
NumericUpDown nud = new NumericUpDown();
nud.Minimum = 0;
nud.Maximum = (((int)SetMember.Vier) * 2) - 1;
SetMember enumValue = (SetMember)value;
nud.Value = (int)enumValue;
editorService.DropDownControl(nud);
result = (SetMember)nud.Value;
}
}
return result;
}
Het geretourneerde object (onze nieuw aangemaakte numeric up/down) representeert de door de ontwikkelaar voorheen gekozen waarde. Maar voordat we zover zijn, vragen we eerst toegang tot een Editor service. Daarna construeren we de te tonen NumericUpDown en vullen we daar de huidige waarde in. Daarna geven we de NumericUpDown door aan de verkregen service. Merk op dat tenslotte de door de gebruiker gekozen waarde weer verkregen en doorgegeven wordt.
Stap 2: Koppelen van de nieuwe editor aan onze property
Is dit dus alles? Bijna, de eigen editor is gebouwd en klaar voor gebruik. Als laatste moet deze aan de nieuwe eigenschap MyProperty gekoppeld worden. Hierbij volstaat het toekennen van het attribuut Editor.
public partial class CustomControl1 : Control
{
…
[Editor(typeof(MultiSetMembersEditor), typeof(UITypeEditor))]
public etMember MyProperty
{
…
}
}
De IDE zal dan via reflection de editor aan de property koppelen. Dat is dus vrij triviaal.
De MultiSetMembersEditor wordt nu als nieuwe UITypeEditor toegekend. Als de ontwikkelaar nu de dropdown kiest, zal de NumericUpDown getoond worden. De huidige waarde is ingevuld en nadat de ontwikkelaar een nieuwe keuze heeft gemaakt, zal deze getoond worden. Bij het invullen van b.v. integer vijftien zullen naderhand alle vier de enumeratie constanten geselecteerd zijn (zie figuur 4).

Fig. 4: De nieuwe editor na keuze van een nieuwe verzameling
Conclusie
Hoewel toepassingen van een FlagsAttribute eigenschap tot op heden redelijk uniek zijn, vind ik het een heel aardige aanvulling bij het bouwen van nieuwe controls en componenten. Zoals in dit artikel gedemonstreerd is, kan een verzameling enumeratie constanten snel en eenvoudig toegepast en uitgevraagd worden en is ook een specifieke editor met een minimale inspanning te bouwen.
Veel succes!