WPF power to the DataTemplateSelector(s)
We encapsulated all the logic of the Wizard in the view model, and obviously we have a bunch of classes the inherits from AbstractWizardPage.class WelcomeWizardViewModel : AbstractViewModel { AbstractWizardPage[] pages; public AbstractWizardPage CurrentPage { get { return this.GetPropertyValue( () => this.CurrentPage ); } private set { this.SetPropertyValue( () => this.CurrentPage, value ); } } public void MoveToNextPage() { var idx = Array.IndexOf( this.pages, this.CurrentPage ); var next = this.pages[ idx + 1 ]; this.CurrentPage = next; this.CurrentPage.OnNavigatedTo( WizardNavigationDirection.Forward ); } public void MoveToPreviousPage() { var idx = Array.IndexOf( this.pages, this.CurrentPage ); var prev = this.pages[ idx - 1 ]; this.CurrentPage = prev; this.CurrentPage.OnNavigatedTo( WizardNavigationDirection.Backward ); } }
The problem now is how can we associate a view to each page? nothing more than this:
where the template selector is something like:<ContentControl Content="{Binding Path=CurrentPage}" Margin="10"> <ContentControl.ContentTemplateSelector> <ts:ConventionTemplateSelector /> </ContentControl.ContentTemplateSelector> </ContentControl>
We have e template selector that delegates to a convention the way to determine which will be the type of the layout we want at runtime, here we are using FrameworkElementFactory in order to generate a DataTemplate that will be linked to a Type.public class ConventionTemplateSelector : DataTemplateSelector { Dictionary<Object, DataTemplate> templatesCache = new Dictionary<Object, DataTemplate>(); public ConventionTemplateSelector() { this.CacheTemplates = true; this.ConventionHandler = new LayoutTemplateConventionHandler(); } public Boolean CacheTemplates { get; set; } public AbstractTemplateConventionHandler ConventionHandler { get; set; } public override DataTemplate SelectTemplate( object item, System.Windows.DependencyObject container ) { if( item != null ) { DataTemplate template; if( !this.templatesCache.TryGetValue( item, out template ) ) { var templateType = this.ConventionHandler.GetTemplateTypeFor( item ); template = new DataTemplate() { VisualTree = new FrameworkElementFactory() { Type = templateType } }; if( this.CacheTemplates ) { this.templatesCache.Add( item, template ); } } return template; } return base.SelectTemplate( item, container ); } }
The abstract template convention handler is nothing more then a really simple class:
and default convention handler is:public abstract class AbstractTemplateConventionHandler { public abstract Type GetTemplateTypeFor( Object item ); }
We are basically looking for a type in the same namespace of the item type with a name that ends with layout, so having a page called WelcomePage we will look for a WelcomePageLayout type to be used as the template view.class LayoutTemplateConventionHandler : AbstractTemplateConventionHandler { public override Type GetTemplateTypeFor( object item ) { var type = item.GetType(); var layoutTypeName = String.Format( "{0}.{1}Layout", type.Namespace, type.Name ); var layoutType = Type.GetType( layoutTypeName, true ); return layoutType; } }
.m