Design Time Data, reloaded.
trovo che sia decisamente produttivo, in particolare per la platea, partecipare ad un corso dove la docenza è in pair, con 2 docenti, con un background tecnico paragonabile, ma con un background lavorativo diverso perchè porta un arricchimento non da poco.…torniamo “in topic” che è meglio :-)
L’obiettivo è poter avere, in Blend, questo:
Quindi semplicemente una design time experience degna del suo nome. L’inghippo, nel mio caso, è che usando M-V-VM, seguendo l’approccio ViewModel first, il ViewModel è fatto più o meno così:
il tutto viene gestito dal fido Castle quindi quel costruttore per me è decisamente friction-less, in ottica design time data però quella cosa è decisamente scomoda principalmente per 2 motivi:sealed class SubjectsListViewModel : ItemsListViewModel<ISubjectsListView, Subject, Subject, Subject>, ISubjectsListViewModel { public SubjectsListViewModel( IDispatcher dispatcher,
ISubjectsListView view,
IMessageBroker broker,
IDataContextFactory dataContextFactory,
ISubjectsManagementService subjectsService,
IUserInteractionService uiService ) : base( dispatcher, view, broker, SelectionType.Extended ) {
...
}
}
- la classe non è pubblica e lo xaml si lamenta se cercate di usarla;
- la classe non ha un costruttore pubblico di default senza parametri e questo la rende definitivamente inutilizzabile via xaml;
- perchè non riprendi quello che hai lasciato in sospeso…?
- e lo adatti a questo scenario che è decisamente più semplice?
quella classe “Sample” è così definita, scroccando un po’ di sintassi a Fluent NHibernate:<Window x:Class="SubjectsListViewDocument" xmlns:design="http://schemas.topics.it/wpf/subjects/design" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d design"> <Window.Resources> <design:Sample x:Key="sample" /> Window.Resources> <Grid d:DataContext="{StaticResource sample}">
e quello che mi resta da fare è limitarmi a fare quello che farei di solito (funziona perfettamente anche nei vari “property editor” di Blend):public class Sample : DesignTimeHost<ISubjectsListViewModel> { public Sample() { this.Expose( o => o.Items ) .WithValue( new EntityView<Subject>( new List<Subject>() { new Person(){ FirstName = "Mauro", LastName = "Servienti" }, new Company(){ CompanyName = "topics.it" } } ) ) .AsReadOnly(); } }
Ottenendo il risultato di cui sopra, comodo semplice e definibile addirittura in un assembly separato. Complicandomi la vita ho cercato di esporre come design time data un modello ben più complesso (un master detail con un grafo decisamente ostico) incappando nei “soliti” non pochi problemi che però stavolta possono essere brillantemente risolti:<ListView ItemsSource="{Binding Path=Items}"...
utilizzando un tool di mocking, del resto siamo a design time, e producendo di fatto qualcosa di editabile in maniera umana, molto umana:var address1 = MockRepository.GenerateStub<IAddressViewModel>(); address1.Street = "sample street 1"; var address2 = MockRepository.GenerateStub<IAddressViewModel>(); address2.Street = "sample street 2"; this.Expose( e => e.Addresses ) .AsReadOnly() .WithValue( new EntityView<IAddressViewModel>( new List<IAddressViewModel>() { address1, address2 } ) );
Finalmente perchè prima quel coso era ridotto ad un groviglio inguardabile, e adesso posso farlo diventare quello che serve diventi.
Adesso la domanda potrebbe essere: ma visto che funziona anche con un tool di mocking perchè non usare solo quello?
Cerca le differenze… :-)
Nonostante quel “pannello di warning” sia così definito:<design:Sample x:Key="sample" DesignTimeAreDisplayedDataStale="[False]/[True]" />
Quella proprietà “DesignTimeAreDisplayedDataStale” non esiste sul modello, e quindi non esisterebbe per il tool di mocking, ma esiste per il DesignTimeHost<Border Grid.Row="0" Background="#FFFFFB9C" Visibility="{Binding AreDisplayedDataStale, Converter={StaticResource boolVisConv}}" BorderBrush="#FFFFF630" BorderThickness="1"
Quindi:public sealed class Sample : DesignTimeHost<ISubjectsListViewModel> { public Sample() { this.Expose( o => o.Items ) .WithValue( new EntityView<Subject>( new List<Subject>() { new Person(){ FirstName = "Mauro", LastName = "Servienti" }, new Company(){ CompanyName = "topics.it" } } ) ) .AsReadOnly(); this.Expose( o => o.AreDisplayedDataStale )
.BoundTo( this, s => s.DesignTimeAreDisplayedDataStale )
.WithLiveValue( () => this.DesignTimeAreDisplayedDataStale ) .AsReadOnly(); } private Boolean _designTimeAreDisplayedDataStale = false; public Boolean DesignTimeAreDisplayedDataStale { get { return this._designTimeAreDisplayedDataStale; } set { if( value != this.DesignTimeAreDisplayedDataStale ) { this._designTimeAreDisplayedDataStale = value; this.OnPropertyChanged( e => e.AreDisplayedDataStale ); } } } }
- la proprietà AreDisplayedDataStale, esposta dal modello, è registrata con un live value, un delegato, e non con un valore statico come le altre;
- viene aggiunta una proprietà DesignTimeAreDisplayedDataStale che non esiste sul modello e che al momento della variazione notifica il suo cambiamento come una normalissima proprietà…;
- …proprietà che il DesignTimeHost
tiene monitorata (BoundTo()) e, al momento della variazione, notifica a sua volta la variazione della proprietà AreDisplayedDataStale…
Concludendo…
Con pochissimo codice siamo in grado di dare a Blend un set di dati, da usare a Design Time, anche molto complesso permettendoci di avere una Design Time Experience veramente di qualità.
.m