…che in quanto tale forse andava fatta prima… :-)

Approfitto dell commento di Nicolò per farla adesso, nonostante ne abbia già parlato anche se mi rendo conto solo ora che probabilmente annegata in un discorso ampio e complesso la cosa possa passare inosservata.

Attenzione… :-)

Osservate il post di Corrado sulla localizzazione:

imageimage

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:

image

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!

public class MyCustomEntity : ICustomTypeDescriptor
{
    public PropertyDescriptorCollection GetProperties( Attribute[] attributes )
    {
        throw new NotImplementedException();
    }

    public PropertyDescriptorCollection GetProperties()
    {
        return this.GetProperties( null );
    }

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.

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:

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 )
    {
        
    }
}

Lascio a voi il tedioso lavoro di scoprire su MSDN il significato, che direi intuitivo, dei vari metodi e delle proprietà.

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();

A questo punto è facile immaginarsi che in quel GetValue() potete fare il bello e cattivo tempo. Il discorso è molto diverso, e pure molto più complesso, se volete prendere in considerazione anche una chiamata di questo tipo:

TypeDescriptor.GetProperties( typeof( MyCustomEntity ) );

Questa da ingannare è decisamente peggio, fattibile (forse) ma molto complessa, e per ora il gioco non vale la candela… ma io non mollo! :-P

Adesso che avete un’abbozzo di infrastruttura potete provare ad usarla:

public PropertyDescriptorCollection GetProperties( Attribute[] attributes )
{
    var property = new MyCustomPropertyDescriptor( "Foo", typeof( String ) );
    return new PropertyDescriptorCollection( new[] { property } );
}

e da Wpf/xaml fare una cosa del tipo:

<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>

Adesso…:

  1. aprite la stessa solution con un altro Visual Studio (il debuggatore);
  2. dal debuggatore attaccate il debugger al Visual Studio di partenza;
  3. dal debuggatore piazzate un po’ di breakpoint;
  4. provate dal “debuggato” ad aprire il designer visuale della Window1.xaml… :-)

Ne riparleremo abbiate fede… :-)

.m

Technorati Tags: ,