Premessa: doverosa :-)
Attenzione… :-)
Osservate il post di Corrado sulla localizzazione:
Se provate, in un qualsiasi punto del ViewModel, a scrivere “this.LocalizedText.” scoprite che l’intellisense non vi fa vedere un bel nulla…eppure questo funziona che è un piacere:
Come è possibile?
Il giochetto è abbastanza semplice ed è basato su qualcosa che esiste da che mondo è mondo nel framework: Reflection.
Buffaldino e pure un po’ sacripante :-)
Se però andiamo a spulciare un po’ meglio scopriamo che non è sempre così… esiste infatti un sistema, che ad esempio la PropertyGrid usa massicciamente, ben più sofisticato: TypeDescriptor;
Il motore di Data Binding di Wpf utilizza un TypeDescriptor nel momento in cui l’oggetto (la sorgente dati) con cui deve andare in binding implementa l’interfaccia ICustomTypeDescriptor.
Ciack si gira!
Facciamo la cosa più semplice possibile, implementiamo su una nostra entità l’interfaccia ICustomTypeDescriptor, noterete che ha una pletora di metodi che in un contesto di data binding fortemente basato su proprietà possiamo tranquillamente ignorare.public class MyCustomEntity : ICustomTypeDescriptor { public PropertyDescriptorCollection GetProperties( Attribute[] attributes ) { throw new NotImplementedException(); } public PropertyDescriptorCollection GetProperties() { return this.GetProperties( null ); }
Essenziale è implementare i 2 metodi GetProperties() implementazione in cui possiamo tranquillamente demandare tutto il lavoro a uno dei 2 oveload, credo non esista nessuno nel framework (forse la sola PropertyGrid) che chiami l’overload di GetProperties che prende un array di attributi passando qualcosa di diverso da null.
Quello che dobbiamo ritornare è una lista di PropertyDescriptor che essendo però abstract non può essere usata direttamente:
Lascio a voi il tedioso lavoro di scoprire su MSDN il significato, che direi intuitivo, dei vari metodi e delle proprietà.public class MyCustomPropertyDescriptor : PropertyDescriptor { public MyCustomPropertyDescriptor( String propertyName, Type propertyType ) : base( propertyName, null ) { this._propertyType = propertyType; } public override bool CanResetValue( object component ) { return false; } public override Type ComponentType { get { return typeof( MyCustomEntity ); } } public override bool IsReadOnly { get { return false; } } public override void ResetValue( object component ) { throw new NotSupportedException(); } public override bool ShouldSerializeValue( object component ) { return false; } Type _propertyType; public override Type PropertyType { get { return this._propertyType; } } public override object GetValue( object component ) { return "Hello World!"; } public override void SetValue( object component, object value ) { } }
I due metodi fondamentali sono GetValue e SetValue, quest’ultimo chiamabile solo se IsReadOnly è false; quello che succede a runtime è che:
- l’infrastruttura di binding si accorge che l’istanza che ha in mano come data source implementa ICustomTypeDescriptor;
- Sull’istanza che ha per “le mani” chiama GetProperties(), o meglio chiama TypeDescriptor.GetProperties( instance ) lasciando al TypeDescriptor l’onere di maneggiare un ICustomTypeDescriptor, per avere l’elenco delle proprietà supportate, e qui potete spudoratamente mentire :-);
- Sulla base della binding expression va alla ricerca della proprietà tra quelle che avete generato e chiama GetValue passandovi l’istanza del componente (la data source) per cui vuole ottenere il valore. Stesso discorso per SetValue();
Questa da ingannare è decisamente peggio, fattibile (forse) ma molto complessa, e per ora il gioco non vale la candela… ma io non mollo! :-PTypeDescriptor.GetProperties( typeof( MyCustomEntity ) );
Adesso che avete un’abbozzo di infrastruttura potete provare ad usarla:
e da Wpf/xaml fare una cosa del tipo:public PropertyDescriptorCollection GetProperties( Attribute[] attributes ) { var property = new MyCustomPropertyDescriptor( "Foo", typeof( String ) ); return new PropertyDescriptorCollection( new[] { property } ); }
Adesso…:<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication1" Title="Window1" Height="300" Width="300"> <Window.DataContext> <local:MyCustomEntity /> Window.DataContext> <Grid> <TextBlock Text="{Binding Path=Foo}" /> Grid> Window>
- aprite la stessa solution con un altro Visual Studio (il debuggatore);
- dal debuggatore attaccate il debugger al Visual Studio di partenza;
- dal debuggatore piazzate un po’ di breakpoint;
- provate dal “debuggato” ad aprire il designer visuale della Window1.xaml… :-)
.m