Poco tempo fa ho parlato della funzionalità di Blend 3 che permette di avere dei dati di esempio a design time al fine di ottimizare proprio la design experience, subito dopo Corrado ha parlato di qualcosa di ancora più figoso.
Purtroppo siamo alle solite: non è tutto oro quel che luccica.
Se avete un ViewModel, o più in generale un DataContext, un po’ complesso la soluzione di Corrado purtroppo non funziona. Per un po’ complesso intendo che basta banalmente esporre dalla classe, che verrà utilizzata come DataContext, un tipo generico e siete “fritti” perchè lo xaml non ha supporto per i generics: IDictionary<T, K> come esempio triviale.
Ma non tutto è perso, siccome è una cosa di cui ho veramente bisogno oggi mi sono dedicato a capire se c’era una soluzione e credo di avere trovato un compromesso accettabile, sicuramente funziona e risolve il problema, anche se non è elegantissimo.
Background
Nel toolkit che ormai mi sono costruito per utilizzare Model-View-ViewModel in applicazioni composite ci sono un bel set di ViewModel di base per le più svariate esigenze, uno ad esempio è così definito:
public interface IItemsListViewModel<TView, TEntity> :
IViewModel<TView>,
ICanHandleAsyncOperationViewModel
where TView : class, IItemsListView
where TEntity : class, IEntity
{
IBindableCommand Select { get; }
IEntityView<TEntity> Items { get; }
IEntityView<TEntity> Selection { get; set; }
IEntityItemView<TEntity> Current { get; }
SelectionType SupportedSelectionType { get; }
void LoadItemsByQuery( IQuery query );
void RefreshQueryResults();
event EventHandler<ItemsByQueryLoadedEventArgs> ItemsByQueryLoaded;
}
Non dettaglio, mi limito semplicemente a dire che se volete visualizzare una lista di elementi con molte funzionalità preimplementate vi basta deinfire una cosa del tipo:
class EstimatesListViewModel :
ItemsListViewModel<IEstimatesListView, IEstimate>
{
public EstimatesListViewModel( IDispatcher dispatcher, IEstimatesListView view, IMessageBroker broker, IDataContextFactory dataContextFactory )
: base( dispatcher, view, broker, SelectionType.Extended, dataContextFactory )
{
}
}
Fine, e il tutto funziona a patto che nel costruttore arrivino le cose giuste ;-) ma se usate un tool per IoC il problema se lo smazza lui.
Quello che ci resta da fare è solo ed esclusivamente disegnare la nostra bella view e sbizzarrirci con la potenza di Wpf.
Il problema
è sempre il solito: per domare la potenza di Wpf avete bisogno di avere un designer che vi faccia vedere dei dati a design-time altrimenti non c’è trippa per gatti siete persi nei meandri dello xaml, dei DataTemplate, degli Styles e dei ControlTemplate.. per non parlare delle “litigate” con TemplateBinding e TemplatedParent :-)
In questo esempio sia la mia soluzione che quella di Corrado non funzionano e Blend tende inesorabilmente a crashare con allegria… io un po’ meno ;-)
Alla fine la soluzione che ho trovato è stata definire in tutta la gerarchia una gerarchia, …, di DesignTimeXxxxViewModel portando a questo:
sealed class DesignTimeEngagementManagerViewModel :
DesignTimeItemsListViewModel<IEngagementManagerView, IAdvertisementReservationViewModel>,
IEngagementManagerViewModel
{
public DesignTimeEngagementManagerViewModel()
{
if( this.GetIsInDesignMode() )
{
var dataSource = new List<DesignTimeAdvertisementReservationViewModel>()
{
new DesignTimeAdvertisementReservationViewModel( ViewModel.UndefinedReservationStatus.Undefined, null),
new DesignTimeAdvertisementReservationViewModel( ViewModel.UndefinedReservationStatus.Undefined, null),
new DesignTimeAdvertisementReservationViewModel( new ViewModel.PublishedReservationStatus(), null)
};
this._items = new EntityView<IAdvertisementReservationViewModel>( dataSource.ToArray() );
}
}
IEntityView<IAdvertisementReservationViewModel> _items;
public override IEntityView<IAdvertisementReservationViewModel> Items
{
get { return this._items; }
}
public IPublication CurrentPublication
{
get { return null; }
}
public string ToolTip
{
get { return "Design time tooltip..."; }
}
public string Title
{
get { return "Design time title."; }
}
}
che ha una differenza sostanziale con quello “vero”… circa 400 righe di codice (sommando quello di tutte le classi derivate), ma non solo, ha un costruttore pubblico senza parametri permettendo di definire nello xaml questo:
<UserControl.Resources>
<dd:DesignTimeEngagementManagerViewModel x:Key="designViewModel" />
</UserControl.Resources>
<local:EngagementManagerDocument TabText="{Binding Title}"
d:DataContext="{StaticResource designViewModel}"
TabToolTip="{Binding ToolTip}">
e ottenendo al volo dentro Blend questo:
che adesso è finalmente disegnabile in maniera accettabile e smetterà di essere quella ciofeca che si vede nell’esempio.
La cosa da notare è che Blend applica correttamente stili e template anche a design-time, si nota infatti che l’ultima “riga” è diversa dalle altre.
Se poi consideriamo che quel sample prima dell’intervento producevano in Blend questo:
ergo il nulla più assoluto…
E’ un compromesso perchè definisco una gerarchia di classi in più anche se è:
- mostruosamente semplice;
- in totale assenza di logica;
- con dati completamente statici;
Alla fine anche nell’esempio di Corrado qualcosa dovete scrivere, li era xaml qui è C# ma poco cambia.
Un’altra soluzione sarebbe quella di usare un tool di mocking ma mi piace poco il requisito di avere una reference alla libreria di mock, inoltre di codice se ne deve scrivere lo stesso quindi non è che ci sia poi sto gran risparmio.
Un piccolo vantaggio è che quei design-time-data possono essere anche usati come “fake” per le suite di test.
Adesso aspetto che Corrado mi smentisca con un’altra soluzione degna delle sue capacità :-)
.m