Come, io, uso le enumerazioni
Domanda: se tutte le proprietà ed i dati devono essere localizzabili gli enum sono ancora cosi necessari?In generale la risposta è si, vediamo nel dettaglio cosa faccio. Ecco come definisco una enumerazione:
quel EnumItemDescriptionAttribute è un normalissimo attributo:public enum AdvertisementInvoiceCadence { None = 0, [EnumItemDescription( "Immediata", "La fattura verrà emessa immediatamente e contestualmente alla creazione del contratto.", 1 )] AgreementContextual = 1, [EnumItemDescription( "In data di pubblicazione", "La fattura verrà emessa dopo la pubblicazione dell'inserzione con data uguale a quella di pubblicazione.", 2 )] PublicationContextual = 2, [EnumItemDescription( "Mensile", "La fatturazione avrà cadenza mensile, ogni mese verrà emessa una fattura per le inserzioni pubblicate nel mese.", 3 )] Monthly = 3, [EnumItemDescription( "Bimestrale", "La fatturazione avrà cadenza bimestrale, ogni 2 mesi verrà emessa una fattura per le inserzioni pubblicate nei 2 mesi precedenti.", 4 )] Bimonthly = 4, [EnumItemDescription( "Trimestrale", "La fatturazione avrà cadenza trimestrale, ogni 3 mesi verrà emessa una fattura per le inserzioni pubblicate nei 3 mesi precedenti.", 5 )] Quarterly = 5, [EnumItemDescription( "Semestrale", "La fatturazione avrà cadenza semestrale, ogni 6 mesi verrà emessa una fattura per le inserzioni pubblicate nei 6 mesi precedenti.", 6 )] HalfYearly = 6 }
Ogni commento è superfluo, solo i due metodi protected virtual OnGetXxxx() sono interessanti perchè servono per gestire la localizzazione, ma non è questo il problema adesso.[AttributeUsage( AttributeTargets.Field, AllowMultiple = false, Inherited = false )] public class EnumItemDescriptionAttribute : Attribute { readonly String _caption; readonly String _description; readonly Int32 _index; public EnumItemDescriptionAttribute( String caption ) : this( caption, String.Empty, -1 ) { } public EnumItemDescriptionAttribute( String caption, Int32 index ) : this( caption, String.Empty, index ) { } public EnumItemDescriptionAttribute( String caption, String description, Int32 index ) { if( caption == null ) { throw new ArgumentNullException( "caption" ); } if( description == null ) { throw new ArgumentNullException( "description" ); } this._caption = caption; this._description = description; this._index = index; } public String Caption { get { return this.OnGetCaption(); } } public String Description { get { return this.OnGetDescription(); } } public virtual Int32 Index { get { return this._index; } } protected virtual String OnGetCaption() { return this._caption; } protected virtual String OnGetDescription() { return this._description; } }
Come utilizziamo il tutto? facciamo un esempio di un ViewModel:
Il ViewModel espone una “lista” di valori, un po’ starni ma siate fiduciosi, e una proprietà che rappresenta il valore selezionato, a runtime quello che succede è questo:public interface IAgreementConversionWizardViewModel : … { IEnumerable<EnumBinder<AdvertisementInvoiceCadence>> KnownAdvertisementInvoiceCadence { get; } AdvertisementInvoiceCadence SelectedAdvertisementInvoiceCadence { get; set; }
… }
prodotto da questo xaml, ma la stessa cosa funziona con Windows Forms e Asp.Net:
Il “barbatrucco” è fatto da “EnumBinder” e soci e si limita a questo:<GroupBox Header="Cadenza"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> Grid.RowDefinitions> <ComboBox ItemsSource="{Binding Path=KnownAdvertisementInvoiceCadence}" DisplayMemberPath="Caption" SelectedValuePath="Value" SelectedValue="{Binding Path=SelectedAdvertisementInvoiceCadence}" /> <ScrollViewer Grid.Row="2" Margin="4" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"> <TextBlock Text="{Binding Path=SelectedAdvertisementInvoiceCadence, Converter={StaticResource enumDescConverter}}" TextWrapping="Wrap" /> ScrollViewer> Grid> GroupBox>
ExtractBindingDatathis.KnownAdvertisementInvoiceCadence = EnumHelper.ExtractBindingData<AdvertisementInvoiceCadence>();
Si potrebbe ipotizzare di creare una MarkupExtension che faccia il giochetto senza doverlo fare a mano nel ViewModel, non è che una riga di codice sia poi sto gran problema.public static IEnumerable<EnumBinder> ExtractBindingData () { return ExtractBindingData ( null ); } public static IEnumerable<EnumBinder > ExtractBindingData ( Predicate filter ) { var lst = new List<EnumBinder >(); Array objs = Enum.GetValues( typeof( T ) ); foreach( Object obj in objs ) { if( filter == null || filter( ( T )obj ) ) { Enum e = ( Enum )obj; EnumItemDescriptionAttribute attribute; if( e.TryGetDescriptionAttribute( out attribute ) ) { var eb = new EnumBinder ( attribute, ( T )obj ); lst.Add( eb ); } } } lst.Sort( ( v1, v2 ) => v1.Index.CompareTo( v2.Index ) ); return lst.AsReadOnly(); }
Quale è il vantaggio di questa soluzione?
- Lato UI ho dei valori umanamente comprensibili;
- Lato “codice” ho dei valori “macchinalmente” :-) comprensibili e garantiti da san Csc.exe!;
- Gestisco tutto in un posto solo semplificando notevolmente la manutenzione perchè non devo ricordarmi di tenere allineati i 2 mondi;
- Esiste infine un LocalizableEnumItemDescriptionAttribute che ha proprio il supporto per la localizzazione e permette di “esternalizzare” le stringhe che nell’esempio sono cablate nel codice;