windows phone 7中动态应用DataTemplate模板


原理:
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.
  1. <Grid Grid.Row="1" x:Name="ContentGrid">
  2.        <ListBox ItemsSource="{Binding Path=Items}"
  3.                 ></ListBox>
  4. </Grid>

The code above will have the following effect when run on the phone.
1.png
 


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:
  1. <phone:PhoneApplicationPage.Resources>
  2.         <DataTemplate x:Key="custTemplate">
  3.             <StackPanel >
  4.                 <TextBlock Text="{Binding LastName}"
  5.                            FontSize="30" Foreground="Red"></TextBlock>
  6.                 <TextBlock Text="{Binding FirstName}"
  7.                            FontSize="25" Foreground="Green"
  8.                            Margin="10,0,0,0"></TextBlock>
  9.             </StackPanel>
  10.         </DataTemplate>
  11.     </phone:PhoneApplicationPage.Resources>
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.
  1. <ListBox ItemsSource="{Binding Path=Items}" Grid.Row="1" Margin="5"
  2.                  ItemTemplate="{StaticResource custTemplate}">
  3.         </ListBox>
复制代码
The effect of this code can be seen in the image below:
2.png

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:
  1. <Window.Resources>
  2.    <DataTemplate DataType="{x:Type loc:Employee}">
  3.      <StackPanel>
  4.       <StackPanel Orientation="Horizontal">
  5.         <TextBlock Text="{Binding LastName}"/>
  6.         <TextBlock Text="{Binding FirstName}"/>                                          
  7. </StackPanel>
  8. <TextBlock Text="{Binding Path=Salary}" x:Name="sal">
  9. </TextBlock>
  10. </StackPanel>
  11. <DataTemplate.Triggers>
  12. <DataTrigger Binding="{Binding Path=Salary}"  Value="90000">
  13. <Setter TargetName="sal" Property="Foreground" Value="Red"/>
  14. <Setter TargetName="sal" Property="FontWeight" Value="Bold"/>
  15. </DataTrigger>
  16. </DataTemplate.Triggers>
  17. </DataTemplate>   
  18. </Window.Resources>

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:
  1. <ListBox ItemsSource="{Binding Path=Items}"
  2.                  HorizontalContentAlignment="Stretch"
  3.                  ></ListBox>

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.
3.png



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:
  1. <phone:PhoneApplicationPage.Resources>
  2.         <DataTemplate x:Key="custTemplate">
  3.             <StackPanel>
  4.                 <TextBlock Text="{Binding LastName}"/>
  5.                 <TextBlock Text="{Binding FirstName}"
  6.                            Margin="10,0,0,0"/>
  7.             </StackPanel>
  8.         </DataTemplate>
  9.     </phone:PhoneApplicationPage.Resources>

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:
  1. <ListBox ItemsSource="{Binding Path=Items}"
  2. ItemTemplate="{StaticResource custTemplate}">
  3. </ListBox>

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:
  1. public static class DataTemplateSelector
  2.     {
  3.         public static DataTemplate GetTemplate(ViewModelBase param)
  4.         {
  5.             Type t = param.GetType();
  6.             return App.Current.Resources[t.Name] as DataTemplate;
  7.         }
  8.     }

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:
  1. <DataTemplate x:Key="FirstViewModel">
  2.             <v:FirstView></v:FirstView>
  3.         </DataTemplate>

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:
  1. <DataTemplate x:Key="FirstViewModel">
  2.             <v:FirstView></v:FirstView>
  3.         </DataTemplate>

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:
  1. <ContentControl Content="{Binding SelectedItem}"
  2.                             ContentTemplate="{Binding Path=SelectedTemplate}"
  3.                                 HorizontalContentAlignment="Stretch"
  4.                                 VerticalContentAlignment="Stretch"></ContentControl>

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:
  1. public DataTemplate SelectedTemplate
  2.         {
  3.             get
  4.             {
  5.                 if (selItem == null)
  6.                     return null;
  7.                 return DataTemplateSelector.GetTemplate(selItem);
  8.             }
  9.         }

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:
  1. SelectableViewModel selItem;
  2.         public SelectableViewModel SelectedItem
  3.         {
  4.             get { return selItem; }
  5.             set
  6.             {
  7.                 if (selItem != value)
  8.                 {
  9.                     selItem = value;
  10.                     RaisePropertyChanged("SelectedItem");
  11.                     RaisePropertyChanged("SelectedTemplate");
  12.                 }
  13.             }
  14.         }

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:
  1. <ContentControl Content="{Binding SelectedItem}"
  2.                             ContentTemplate="{Binding Path=SelectedTemplate}"
  3.                                 HorizontalContentAlignment="Stretch"
  4.                                 VerticalContentAlignment="Stretch"></ContentControl>
  5.             <ListBox ItemsSource="{Binding Path=Items}" Grid.Row="1" Margin="5"
  6.                      SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}">
  7.                 <ListBox.ItemTemplate>
  8.                     <DataTemplate>
  9.                         <TextBlock Text="{Binding Path=DisplayName}" FontSize="30"
  10.                                    FontWeight="Bold" Margin="5"/>
  11.                     </DataTemplate>
  12.                 </ListBox.ItemTemplate>
  13.                 <ListBox.ItemsPanel>
  14.                     <ItemsPanelTemplate>
  15.                         <StackPanel Orientation="Horizontal"
  16.                                     HorizontalAlignment="Center"></StackPanel>
  17.                     </ItemsPanelTemplate>
  18.                 </ListBox.ItemsPanel>
  19.             </ListBox>

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:
2.png

The mappings between the view models and the views for the 3 options can be seen in the XAML below:
  1. <DataTemplate x:Key="FirstViewModel">
  2.             <v:FirstView></v:FirstView>
  3.         </DataTemplate>
  4.         <DataTemplate x:Key="SecondViewModel">
  5.             <v:SecondView></v:SecondView>
  6.         </DataTemplate>
  7.         <DataTemplate x:Key="ThirdViewModel">
  8.             <v:ThirdView></v:ThirdView>
  9.         </DataTemplate>

  1. Removing the DataTemplate from the ViewModel

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:
  1. public class DynamicContentControl:ContentControl
  2.     {
  3.         protected override void
  4.             OnContentChanged(object oldContent, object newContent)
  5.         {
  6.             base.OnContentChanged(oldContent, newContent);
  7.             this.ContentTemplate =
  8.                 DataTemplateSelector.GetTemplate(newContent as ViewModelBase);
  9.         }        
  10.     }  

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:
  1. <loc:DynamicContentControl Content="{Binding SelectedItem}"
  2.                                 HorizontalContentAlignment="Stretch"
  3.                                 VerticalContentAlignment="Stretch" />

As you can see, I' m not binding the ContentTemplate property anymore. Also the SelectedTemplate property from the ViewModel has been removed.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPFDataTemplateSelector是一个非常有用的工具,它允许我们根据数据类型或其他条件来选择使用哪个DataTemplate来呈现数据。使用DataTemplateSelector的步骤如下: 1. 创建一个继承自DataTemplateSelector的类: ``` public class MyDataTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item is MyType1) { return (DataTemplate)Application.Current.Resources["MyType1Template"]; } else if (item is MyType2) { return (DataTemplate)Application.Current.Resources["MyType2Template"]; } else { return null; } } } ``` 2. 在XAML使用DataTemplateSelector: ``` <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemTemplateSelector> <local:MyDataTemplateSelector/> </ItemsControl.ItemTemplateSelector> </ItemsControl> ``` 动态绑定可以让我们在运行时动态地改变数据绑定的目标或源。使用动态绑定的步骤如下: 1. 创建一个继承自INotifyPropertyChanged接口的类,该类包含需要动态绑定的属性。 2. 在XAML使用Binding对象绑定属性: ``` <TextBlock Text="{Binding MyProperty}"/> ``` 3. 在代码动态更改属性的值: ``` MyObject.MyProperty = "New Value"; ``` DataTemplate.Triggers是一个非常有用的工具,它允许我们根据数据的某些属性来更改DataTemplate的样式。使用DataTemplate.Triggers的步骤如下: 1. 在DataTemplate添加Trigger对象: ``` <DataTemplate x:Key="MyTemplate"> <Border BorderThickness="1" BorderBrush="Black"> <TextBlock Text="{Binding}"> <TextBlock.Style> <Style TargetType="{x:Type TextBlock}"> <Style.Triggers> <DataTrigger Binding="{Binding Path=Status}" Value="Error"> <Setter Property="Foreground" Value="Red"/> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </Border> </DataTemplate> ``` 2. 在数据对象添加属性: ``` public string Status { get; set; } ``` 3. 在代码更改属性的值: ``` MyObject.Status = "Error"; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值