Typed Data Templates in Silverlight
My understanding is that Silverlight does not support DataTemplates with a DataType attribute.
How then would you accomplish the following in SL (author is Josh Smith, full link below). In a nutshell, he's saying that if you bind a TabControl's tab pages to a collection of ViewModels, WPF will figure out how to display each one on the fly by looking for a DataTemplate that has the appropriate (matching) DataType set. Way cool, but I'm wondering how you would (could?) do this in Silverlight.
Applying a View to a ViewModel
MainWindowViewModel indirectly adds and removes WorkspaceViewModel objects to and from the main window's TabControl. By relying on data binding, the Content property of a TabItem receives a ViewModelBase-derived object to display. ViewModelBase is not a UI element, so it has no inherent support for rendering itself. By default, in WPF a non-visual object is rendered by displaying the results of a call to its ToString method in a TextBlock. That clearly is not what you need, unless your users have a burning desire to see the type name of our ViewModel classes!
You can easily tell WPF how to render a ViewModel object by using typed DataTemplates. A typed DataTemplate does not have an x:Key value assigned to it, but it does have its DataType property set to an instance of the Type class. If WPF开发者_运维知识库 tries to render one of your ViewModel objects, it will check to see if the resource system has a typed DataTemplate in scope whose DataType is the same as (or a base class of) the type of your ViewModel object. If it finds one, it uses that template to render the ViewModel object referenced by the tab item's Content property.
The MainWindowResources.xaml file has a ResourceDictionary. That dictionary is added to the main window's resource hierarchy, which means that the resources it contains are in the window's resource scope. When a tab item's content is set to a ViewModel object, a typed DataTemplate from this dictionary supplies a view (that is, a user control) to render it, as shown in Figure 10.in Figure 10.
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx in Figure 10.
Here is ONE way you can do it. I have used a technique like this in the past, and had great success with it.
Consider a very simple container that will create the view for you like this:
public class ViewMapper : ContentControl
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property.Name == "DataContext")
WhenDataContextChanges();
}
private void WhenDataContextChanges()
{
if (DataContext == null)
Content = null;
else
Content = ViewFactory.GetView(DataContext.GetType());
}
}
EDIT
So, you can use this control to do the mapping for you:
<Border DataContext="{Binding MyViewModel}">
<ViewMapper />
</Border>
END EDIT
Note that ViewMapper
simply waits for the data context to change, looks up the appropriate view for the data type, and creates a new one. It relies on ViewFactory, which is a very simple static lookup that maps types to views:
public class ViewFactory
{
private static readonly Dictionary<string, Func<UIElement>> _registry = new Dictionary<string, Func<UIElement>>();
private static string Key(Type viewModelType)
{
return viewModelType.FullName;
}
public static void RegisterView(Type viewModelType, Func<UIElement> createView)
{
_registry.Add(Key(viewModelType), createView);
}
public static UIElement GetView(Type viewModelType)
{
var key = Key(viewModelType);
if (!_registry.ContainsKey(key))
return null;
return _registry[key]();
}
}
Then, you simply need to register the view mappings some place:
ViewFactory.RegisterView(typeof(SomeViewModel), () => new SomeView());
Note that ViewFactory could just as easily use Activator.CreateInstance
instead of using the Func mechanism. Take that one step further, and you can use an IoC container... You could always decide to map via a string Name property on the ViewModel instead of a type... the possibilities are endless and powerful here.
精彩评论