上文说到 可以使用DataTemplateSelector。
其实等于是用 DataTemplateSelector + 动态创建DataTemplate来实现。
using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Markup; namespace ContentDemo { class ContentDataTemplateSelector : DataTemplateSelector { //用来标示一下已经创建过的类型 private readonly static Queue<Type> ViewModelQueue = new Queue<Type>(); public override DataTemplate SelectTemplate(object item, DependencyObject container) { do { if (item == null || item is UIElement) { break; } // item 就是你的ViewModel的类型,在这主要就是找到ViewModel对应的View // 好多框架中会在启动时构建自己的Map,来对应ViewModel // 比如 Caliburn.Micro 框架中默认 是以名称对应的 XXXViewModel 和 XXXView,来构建Map的 // MvvmCross 框架中不仅提供了 名称,还提供了 Attribute的对应方式 // 在这为了演示方便,我就直接反射对应名称的 View了。 var viewModelType = item.GetType(); if (ViewModelQueue.Contains(viewModelType)) { break; } var viewModelName = viewModelType.Name; string name = null; var index = viewModelName.LastIndexOf("ViewModel", StringComparison.OrdinalIgnoreCase); if (index > 0) { name = viewModelName.Substring(0, index); } if (string.IsNullOrEmpty(name)) { break; } var viewName = string.Format("{0}.{1}{2}", viewModelType.Namespace, name, "View"); var view = viewModelType.Assembly.GetType(viewName, false, true); if (view != null) { var dataTemplate = CreateDataTemplate(view, viewModelType); var dtkey = dataTemplate.DataTemplateKey; if (dtkey != null) { Application.Current.Resources.Add(dtkey, dataTemplate); ViewModelQueue.Enqueue(viewModelType); } return dataTemplate; } } while (false); return base.SelectTemplate(item, container); } /// <summary> /// 创建DataTemplate /// </summary> /// <param name="viewType"></param> /// <param name="viewModelType"></param> /// <returns></returns> private DataTemplate CreateDataTemplate(Type viewType, Type viewModelType) { const string xamlTemplate = "<DataTemplate DataType="{{x:Type vm:{0}}}"><v:{1} /></DataTemplate>"; var xaml = String.Format(xamlTemplate, viewModelType.Name, viewType.Name); var context = new ParserContext(); context.XamlTypeMapper = new XamlTypeMapper(new string[0]); context.XamlTypeMapper.AddMappingProcessingInstruction("vm", viewModelType.Namespace, viewModelType.Assembly.FullName); context.XamlTypeMapper.AddMappingProcessingInstruction("v", viewType.Namespace, viewType.Assembly.FullName); context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml"); context.XmlnsDictionary.Add("vm", "vm"); context.XmlnsDictionary.Add("v", "v"); var template = (DataTemplate)XamlReader.Parse(xaml, context); return template; } } }
WPF有一个好处就是他的DataType,所以 你也不一定非要在选择器中处理,你可以在任意你想要的时机时,把DataTemplate加到资源里就可以了。
但是WinRT中,的DataTemplate,就没有DataType,就得通过 选择器来,返回对应的DataTemplate.
还有 WinRT中 不支持 ParserContext,就只能拼字符串构造了。
Xaml:
<Window x:Class="ContentDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ContentDemo" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:MainViewModel x:Key="MainViewModel" /> <!--<DataTemplate DataType="{x:Type local:FirstViewModel}"> <local:FirstView /> </DataTemplate> <DataTemplate DataType="{x:Type local:SecondViewModel}"> <local:SecondView /> </DataTemplate>--> <local:ContentDataTemplateSelector x:Key="ContentDataTemplateSelector" /> </Window.Resources> <Grid DataContext="{StaticResource MainViewModel}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ContentControl Grid.ColumnSpan="2" Content="{Binding Path=ViewModel}" ContentTemplateSelector="{StaticResource ContentDataTemplateSelector}" /> <Button Grid.Row="1" Grid.Column="0" Content="ViewModel 1" Command="{Binding Path=FirstCommand}"/> <Button Grid.Row="1" Grid.Column="1" Content="ViewModel 2" Command="{Binding Path=SecondCommand}"/> </Grid> </Window>
其实要是,用了很多的 ContentControl ,每一个 都去绑定 Selector,还是很烦人的一件事。
这时候,我们可以加一个 ContentControl 的 默认样式,让他默认就使用这个Selector.
<Application x:Class="ContentDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ContentDemo" StartupUri="MainWindow.xaml"> <Application.Resources> <local:ContentDataTemplateSelector x:Key="ContentDataTemplateSelector" /> <Style TargetType="{x:Type ContentControl}"> <Setter Property="ContentTemplateSelector" Value="{StaticResource ContentDataTemplateSelector}"/> </Style> </Application.Resources> </Application>
<Window x:Class="ContentDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ContentDemo" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:MainViewModel x:Key="MainViewModel" /> <!--<DataTemplate DataType="{x:Type local:FirstViewModel}"> <local:FirstView /> </DataTemplate> <DataTemplate DataType="{x:Type local:SecondViewModel}"> <local:SecondView /> </DataTemplate>--> <!--<local:ContentDataTemplateSelector x:Key="ContentDataTemplateSelector" />--> </Window.Resources> <Grid DataContext="{StaticResource MainViewModel}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ContentControl Grid.ColumnSpan="2" Content="{Binding Path=ViewModel}" /> <Button Grid.Row="1" Grid.Column="0" Content="ViewModel 1" Command="{Binding Path=FirstCommand}"/> <Button Grid.Row="1" Grid.Column="1" Content="ViewModel 2" Command="{Binding Path=SecondCommand}"/> </Grid> </Window>
源码:Code