原理:
1. custom ContentControl,定义SelectedTemplate属性,
2. 其他地方可以设置这个属性,
3. xaml中设置ContentTemplate="{Binding Path=SelectedTemplate},即让ContentTemplate绑定SelectedTemplate
原文为e文的,来自codeproject,希望对大家有帮助
Introduction
The article will present a way to dynamically apply data templates by type on the Windows Phone 7 platform. The article will present the current inconvenience with data templates in WP7 and then it will present a solution to this inconvenience. The solution also makes use of the advantages provided by the MVVM pattern. The MVVM framework used in the sample is MVVM Light.
Introducing Data Templates
A data template is a piece of XAML markup that is used to specify the way a data bound object will be displayed in the UI. In WPF, Silverlight and WP7 data templates can be applied on 2 different types of controls. They can be applied to content controls through the ContentTemplate property or they can be applied to items controls through the ItemTemplate property. In the case of the items controls, the data template will be used to display each item in the collection of elements that has been supplied through the ItemsSource property.
If, for example we have a list of strings that we want to display in a UI ListBox control, all we need to do is set that control’s ItemsSource property. The ListBox will automatically know how to display strings and we get what we want. The XAML code for this can be seen in the listing below.
The code above will have the following effect when run on the phone.
Things get a little more complicated if you have a list of complex objects and you need to display some of the properties of each object. This is where data templates are very useful. Say that we had a list of Customer objects that we wanted to display. Let’s also say that for each Customer we want to display the first and last names. To do this, we will need a data template. The data template we will use can be seen in the listing below:
The first thing to note in the listing above is that the data template is added as a page resource. Another thing to note is that this data template specifies that the last name will be displayed on top of the first name and that it will be displayed with a larger font size and also with a different color. Like I said before, in the case of an items control, the data template will be applied to the ItemTemplate property of the items control. This can be seen in the listing below.
复制代码
The effect of this code can be seen in the image below:
Data Templates in WPF vs WP7
Unlike in the related platforms (WPF and Silverlight), data templates in WP7 are very limited. In WPF and Silverlight, data templates are very flexible. They can be defined in place or as resources. They can also be applied manually or automatically by type. On these platforms, data templates also support triggers. An example of a relatively complex data template written for WPF can be seen in the listing below:
The first thing to notice here is that the data template is added as a resource. This allows the data template to be defined once and be used in multiple places. The second thing to notice is that the template only sets the DataType property. There is nothing here about the Key property. This allows the framework to automatically apply this template anywhere there is a need to display the objects for which it is defined. The listing below presents the ListBox that will be used to display the elements:
As you can see, there is no mention here of a data template. The data template will be applied automatically. This can be seen in the image below. You can see that only the entries with a salary of 9000 have the red foreground.
In the WP7 platform, data templates aren’t so advanced. In WP7, we can define data templates in place or as resources. But this is about it. There are no triggers and there is no possibility of applying the data templates automatically by type. This means that when adding data templates as resources, we must specify the x:Key property. An example of such a data template can be seen in the listing below:
To apply this template to a list box, we need to set the ItemTemplate property by using the StaticResource markup expression. This can be seen in the listing below:
The Problem
The lack of options in the WP7 data templates severely limits a developer’s capabilities. In a real world application, there is often the need to display data of different types in the same control, be it a content control or an items control. Since the data that will be displayed is selected at runtime, not being able to dynamically change the data templates presents a problem. Let’s say for example that we have a list of options and based on the selected option, a different object will be displayed in another control. This is clearly a case where dynamically applying data templates will solve the problem immediately. But since WP7 doesn’t natively support this option for data templates, this scenario is somewhat difficult to solve.
The Solution
The solution to the data template problem is to find a way to return a data template based on the type of the selected item and to apply that data template to the control that is to display that item. The data template is retrieved by using the DataTemplateSelector class. This is a static class that has a single static method. The code for this class can be seen in the listing below:
As you can see from the code above, there is a single static method. The method will get the selected item as an argument, will retrieve the item type and based on the type name, it will return the appropriate data template. The solution also takes advantage of the convention over configuration design paradigm. The only convention here is that the data templates must have keys with the same name as the type name of the type they need to display. An example of this can be seen in the listing below:
As you can see from the code above, there is a single static method. The method will get the selected item as an argument, will retrieve the item type and based on the type name, it will return the appropriate data template. The solution also takes advantage of the convention over configuration design paradigm. The only convention here is that the data templates must have keys with the same name as the type name of the type they need to display. An example of this can be seen in the listing below:
As you can see, this data template’s key is “FirstViewModel”. This means that this data template will be used to display view models of type FirstViewModel. This is what the GetTemplate() method does. Based on the type of the argument, it returns a data template that has the same key as the name of the type. In the listing above, the FirstView is the user control used for display.
The data template retrieved by this method will be data bound to the ContentTemplate property of the ContentControl that will be used to display the object. This can be seen in the listing below:
In the listing above, you can see that both the content and the template are bound to properties in the view model. The SelectedItem property represents the element that is currently selected and that needs to be displayed and the SelectedTemplate is the corresponding data template that was retrieved with the GetTemplate() method. This property that gets the data template can be seen in the listing below:
Even though putting UI related code in the ViewModel is in most cases not a good idea, I think it is allowable in this case. This is because there is no actual UI stuff in the property. The templates are defined in XAML and also the property doesn’t actually use UI code that is specifically related to a particular view. It uses a general data template. The last thing to look at is how does the data template changes based on the selected item. This is resolved by the SelectedItem property. The code for this property can be seen in the listing below:
As you can see from the code above, besides announcing that the SelectedItem property has changed, the code also announces that the SelectedTemplate property has changed. This makes the framework reread the property that exposes the data template. When the new data template is applied, the item is displayed correctly.
The Test Application
To test the solution, I built a simple tabbed UI. Based on the selected tab, the page will show a different user control. Because there is no TabControl in WP7, I will use a combination of a ListBox and a ContentControl. The important part of the page UI can be seen in the listing below:
You can see from the code above that we have a ContentControl at the top of the page. This content control will be used to display the data based on the selected item in a list. The Content property is data bound to the SelectedItem property in the view model. This will be changed every time a new item is selected from the list. The ContentTemplate property is also data bound, but this time to the SelectedTemplate view model property. This will set the corresponding data template.
Below the content control is a list box. This list box will be used to present the tabs. The tabs will have a horizontal alignment. The tabs names will be bound to the DisplayName property of the items. Also when the selected item in the list box changes, so will the item that is displayed by the content control above.
The image below presents the main screen of the test application:
The mappings between the view models and the views for the 3 options can be seen in the XAML below:
In order to remove the data template from the view model, the content control that is used to display the content must be changed. One of the options is to derive another control from the content control and override the OnContentChanged() method. This method gets called every time the Content property of the control changes. In my opinion, this is a good place to put the data template selection code. The new control class can be seen in the code below:
As you can see from the code above, I first call the base class implementation, then I use the DataTemplateSelector class to set the ContentTemplate property based on the type of the new content.
Using this new control in XAML is pretty straightforward. The code can be seen below:
As you can see, I' m not binding the ContentTemplate property anymore. Also the SelectedTemplate property from the ViewModel has been removed.
1. custom ContentControl,定义SelectedTemplate属性,
2. 其他地方可以设置这个属性,
3. xaml中设置ContentTemplate="{Binding Path=SelectedTemplate},即让ContentTemplate绑定SelectedTemplate
原文为e文的,来自codeproject,希望对大家有帮助
Introduction
The article will present a way to dynamically apply data templates by type on the Windows Phone 7 platform. The article will present the current inconvenience with data templates in WP7 and then it will present a solution to this inconvenience. The solution also makes use of the advantages provided by the MVVM pattern. The MVVM framework used in the sample is MVVM Light.
Introducing Data Templates
A data template is a piece of XAML markup that is used to specify the way a data bound object will be displayed in the UI. In WPF, Silverlight and WP7 data templates can be applied on 2 different types of controls. They can be applied to content controls through the ContentTemplate property or they can be applied to items controls through the ItemTemplate property. In the case of the items controls, the data template will be used to display each item in the collection of elements that has been supplied through the ItemsSource property.
If, for example we have a list of strings that we want to display in a UI ListBox control, all we need to do is set that control’s ItemsSource property. The ListBox will automatically know how to display strings and we get what we want. The XAML code for this can be seen in the listing below.
- <Grid Grid.Row="1" x:Name="ContentGrid">
- <ListBox ItemsSource="{Binding Path=Items}"
- ></ListBox>
- </Grid>
Things get a little more complicated if you have a list of complex objects and you need to display some of the properties of each object. This is where data templates are very useful. Say that we had a list of Customer objects that we wanted to display. Let’s also say that for each Customer we want to display the first and last names. To do this, we will need a data template. The data template we will use can be seen in the listing below:
- <phone:PhoneApplicationPage.Resources>
- <DataTemplate x:Key="custTemplate">
- <StackPanel >
- <TextBlock Text="{Binding LastName}"
- FontSize="30" Foreground="Red"></TextBlock>
- <TextBlock Text="{Binding FirstName}"
- FontSize="25" Foreground="Green"
- Margin="10,0,0,0"></TextBlock>
- </StackPanel>
- </DataTemplate>
- </phone:PhoneApplicationPage.Resources>
- <ListBox ItemsSource="{Binding Path=Items}" Grid.Row="1" Margin="5"
- ItemTemplate="{StaticResource custTemplate}">
- </ListBox>
Data Templates in WPF vs WP7
Unlike in the related platforms (WPF and Silverlight), data templates in WP7 are very limited. In WPF and Silverlight, data templates are very flexible. They can be defined in place or as resources. They can also be applied manually or automatically by type. On these platforms, data templates also support triggers. An example of a relatively complex data template written for WPF can be seen in the listing below:
- <Window.Resources>
- <DataTemplate DataType="{x:Type loc:Employee}">
- <StackPanel>
- <StackPanel Orientation="Horizontal">
- <TextBlock Text="{Binding LastName}"/>
- <TextBlock Text="{Binding FirstName}"/>
- </StackPanel>
- <TextBlock Text="{Binding Path=Salary}" x:Name="sal">
- </TextBlock>
- </StackPanel>
- <DataTemplate.Triggers>
- <DataTrigger Binding="{Binding Path=Salary}" Value="90000">
- <Setter TargetName="sal" Property="Foreground" Value="Red"/>
- <Setter TargetName="sal" Property="FontWeight" Value="Bold"/>
- </DataTrigger>
- </DataTemplate.Triggers>
- </DataTemplate>
- </Window.Resources>
- <ListBox ItemsSource="{Binding Path=Items}"
- HorizontalContentAlignment="Stretch"
- ></ListBox>
In the WP7 platform, data templates aren’t so advanced. In WP7, we can define data templates in place or as resources. But this is about it. There are no triggers and there is no possibility of applying the data templates automatically by type. This means that when adding data templates as resources, we must specify the x:Key property. An example of such a data template can be seen in the listing below:
- <phone:PhoneApplicationPage.Resources>
- <DataTemplate x:Key="custTemplate">
- <StackPanel>
- <TextBlock Text="{Binding LastName}"/>
- <TextBlock Text="{Binding FirstName}"
- Margin="10,0,0,0"/>
- </StackPanel>
- </DataTemplate>
- </phone:PhoneApplicationPage.Resources>
- <ListBox ItemsSource="{Binding Path=Items}"
- ItemTemplate="{StaticResource custTemplate}">
- </ListBox>
The lack of options in the WP7 data templates severely limits a developer’s capabilities. In a real world application, there is often the need to display data of different types in the same control, be it a content control or an items control. Since the data that will be displayed is selected at runtime, not being able to dynamically change the data templates presents a problem. Let’s say for example that we have a list of options and based on the selected option, a different object will be displayed in another control. This is clearly a case where dynamically applying data templates will solve the problem immediately. But since WP7 doesn’t natively support this option for data templates, this scenario is somewhat difficult to solve.
The Solution
The solution to the data template problem is to find a way to return a data template based on the type of the selected item and to apply that data template to the control that is to display that item. The data template is retrieved by using the DataTemplateSelector class. This is a static class that has a single static method. The code for this class can be seen in the listing below:
- public static class DataTemplateSelector
- {
- public static DataTemplate GetTemplate(ViewModelBase param)
- {
- Type t = param.GetType();
- return App.Current.Resources[t.Name] as DataTemplate;
- }
- }
- <DataTemplate x:Key="FirstViewModel">
- <v:FirstView></v:FirstView>
- </DataTemplate>
- <DataTemplate x:Key="FirstViewModel">
- <v:FirstView></v:FirstView>
- </DataTemplate>
The data template retrieved by this method will be data bound to the ContentTemplate property of the ContentControl that will be used to display the object. This can be seen in the listing below:
- <ContentControl Content="{Binding SelectedItem}"
- ContentTemplate="{Binding Path=SelectedTemplate}"
- HorizontalContentAlignment="Stretch"
- VerticalContentAlignment="Stretch"></ContentControl>
- public DataTemplate SelectedTemplate
- {
- get
- {
- if (selItem == null)
- return null;
- return DataTemplateSelector.GetTemplate(selItem);
- }
- }
- SelectableViewModel selItem;
- public SelectableViewModel SelectedItem
- {
- get { return selItem; }
- set
- {
- if (selItem != value)
- {
- selItem = value;
- RaisePropertyChanged("SelectedItem");
- RaisePropertyChanged("SelectedTemplate");
- }
- }
- }
The Test Application
To test the solution, I built a simple tabbed UI. Based on the selected tab, the page will show a different user control. Because there is no TabControl in WP7, I will use a combination of a ListBox and a ContentControl. The important part of the page UI can be seen in the listing below:
- <ContentControl Content="{Binding SelectedItem}"
- ContentTemplate="{Binding Path=SelectedTemplate}"
- HorizontalContentAlignment="Stretch"
- VerticalContentAlignment="Stretch"></ContentControl>
- <ListBox ItemsSource="{Binding Path=Items}" Grid.Row="1" Margin="5"
- SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}">
- <ListBox.ItemTemplate>
- <DataTemplate>
- <TextBlock Text="{Binding Path=DisplayName}" FontSize="30"
- FontWeight="Bold" Margin="5"/>
- </DataTemplate>
- </ListBox.ItemTemplate>
- <ListBox.ItemsPanel>
- <ItemsPanelTemplate>
- <StackPanel Orientation="Horizontal"
- HorizontalAlignment="Center"></StackPanel>
- </ItemsPanelTemplate>
- </ListBox.ItemsPanel>
- </ListBox>
Below the content control is a list box. This list box will be used to present the tabs. The tabs will have a horizontal alignment. The tabs names will be bound to the DisplayName property of the items. Also when the selected item in the list box changes, so will the item that is displayed by the content control above.
The image below presents the main screen of the test application:
The mappings between the view models and the views for the 3 options can be seen in the XAML below:
- <DataTemplate x:Key="FirstViewModel">
- <v:FirstView></v:FirstView>
- </DataTemplate>
- <DataTemplate x:Key="SecondViewModel">
- <v:SecondView></v:SecondView>
- </DataTemplate>
- <DataTemplate x:Key="ThirdViewModel">
- <v:ThirdView></v:ThirdView>
- </DataTemplate>
- Removing the DataTemplate from the ViewModel
- public class DynamicContentControl:ContentControl
- {
- protected override void
- OnContentChanged(object oldContent, object newContent)
- {
- base.OnContentChanged(oldContent, newContent);
- this.ContentTemplate =
- DataTemplateSelector.GetTemplate(newContent as ViewModelBase);
- }
- }
Using this new control in XAML is pretty straightforward. The code can be seen below:
- <loc:DynamicContentControl Content="{Binding SelectedItem}"
- HorizontalContentAlignment="Stretch"
- VerticalContentAlignment="Stretch" />