65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 T4 生成 Windows Phone 7 设置页面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.24/5 (7投票s)

2013年11月12日

CPOL

3分钟阅读

viewsIcon

16639

downloadIcon

170

一种用于 Windows Phone 标准化和快速生成设置页面的优雅解决方案

引言

设置页面是几乎每个手机应用程序都必须提供的功能。我正在试验 Windows Phone 8 SDK,并发现了几篇很好的文章,解释了如何为 Windows Phone 创建设置页面(来自 开发中心 的文章非常有帮助)。我很快就对这种方法感到厌倦,因为我正在试验的应用程序有大量的用户设置。因此,我想知道是否可以使用一些代码生成工具(例如 T4)来生成这些代码行。 想法是提供一个定义设置列表的 XML 文件和一些生成视图和模型的 T4。

例如,类似于这样

<?xml version="1.0" encoding="utf-8" ?>
<SettingsScreen Type="Pivot" Title="SETTINGS" 
Namespace="JuiceOntour.T4Settings.Settings">

  <SettingsCategory Key="NotifyCategory" Title="notifications" 
  Summary="define how you want to be notified.">
    <ToggleSwitchItem Key="DoNotify" Title="Notification" 
    Summary="You will be notified when something happens." 
    DefaultValue="true"/>
    <SmallTextBoxItem Key="NotificationTimeout" 
    Title="Timeout (ms)" Summary="" DefaultValue="500" 
    Dependency="DoNotify" DependencyIndent="true"/>
    <ToggleSwitchItem Key="KeepLog" Title="Keep log" 
    Summary="The app will keep a log of all notifications." 
    DefaultValue="false"/>
  </SettingsCategory>

  <SettingsCategory Key="CloudCategory" Title="cloud" 
  Summary="The app is able to connect to the cloud.">
    <ToggleSwitchItem Key="DoCloud" 
    Title="Connect to the cloud" Summary="" 
    DefaultValue="false"/>
    <ToggleSwitchItem Key="Backup" 
    Title="Backup to the cloud" Summary="" 
    DefaultValue="false" Dependency="DoCloud" 
    DependencyIndent="false"/>
  </SettingsCategory>

</SettingsScreen>

最终会变成这样

背景

此解决方案大量使用了 Oleg Sych 关于如何从单个 T4 模板生成多个输出的文章 (http://www.olegsych.com/2008/03/how-to-generate-multiple-outputs-from-single-t4-template/)。

Using the Code

代码主要由一组 T4 组成:每个输出文件一个。只需将提供的代码添加到您的 Windows Phone 解决方案中,然后让 T4 发挥作用即可。

请注意删除每个 T4 的“自定义工具”属性,但 Settings.tt 才能避免不必要的代码生成。

SettingsScreen 节点

SettingsScreen 节点具有以下强制性属性

  • Namespace:标识生成代码的命名空间
  • Type:标识设置屏幕的类型(在附带的代码中支持 PivotSubPages
  • Title:屏幕的标题。 请注意,支持绑定。

SettingsCategory 节点

SettingsCategory 节点具有以下强制性属性

  • Key:标识类别
  • Title:类别的标题。 请注意,支持绑定。
  • Summary:类别的摘要。 请注意,支持绑定。

设置项

附带的代码支持以下设置项

  • ToggelSwitchItem(在 Windows Phone 工具包中)
  • TextBoxItem
  • SmallTextBoxItem(与 textBox 相同,但保持在一行)
  • ListItem(在 Windows Phone 工具包中)

通用的和强制的属性如下

  • Key:标识该项
  • Title:该项的标题。 请注意,支持绑定。
  • Summary:该项的摘要。 请注意,支持绑定。
  • DefaultValue:默认值。

任何项目都可以使用以下非强制性属性

  • Dependency:将任何 ToggleSwitchItem 的键作为值。当指定的 ToggleSwitch 关闭时,相关项将被禁用。
  • DependencyIndent:如果将值设置为 true,则会缩进该项目。

ListItem 接受一个额外的强制性属性:ItemsSource。 同样,支持绑定。

深入研究 

主模板是 Settings.tt,它负责编排。XML 文件被解析,其内容被存储并通过 remoting CallContext 访问。 最后,处理不同的模板并生成其输出文件。 

...

//parse the XML source file
XDocument xmlDoc = ParseXml("AppSettings.xml");

//pass the parameters to standalone templates (see Oleg Sych's article)
CallContext.SetData("T4Settings.Parameter", xmlDoc);

//process the ViewModel template
ProcessTemplate("AppSettingsModel.tt", "AppSettings.cs");
	
//process the View template
if(GetSettingsScreen(xmlDoc).Attribute(TYPE_ATTRIBUTE).Value.Equals(PIVOT_SCREEN)){
	ProcessTemplate("PivotScreen.tt", "SettingsMainPage.xaml");
	ProcessTemplate("PivotScreenCS.tt", "SettingsMainPage.xaml.cs");
}

...

还有另一个重要的模板,用于访问 XML 文件中的元素:XDocumentParser.tt。 该模板还提供了一些常用的常量,例如节点和属性名称。

...

string NAMESPACE_ATTRIBUTE = "Namespace";
string KEY_ATTRIBUTE = "Key";
string TITLE_ATTRIBUTE = "Title";
string SUMMARY_ATTRIBUTE = "Summary";

...

string TOGGLESWITCH_ITEM = "ToggleSwitchItem";
string SMALLTEXTBOX_ITEM = "SmallTextBoxItem";

...

//get SettingsScreen node
XElement GetSettingsScreen(XDocument doc)
{
	if(doc == null) return null;
	return doc.Descendants("SettingsScreen").First();
}

//get the settings categories
IEnumerable<XElement> GetCatregories(XDocument doc)
{
	if(doc == null) return null;
	return doc.Descendants("SettingsCategory");
}

//get the settings items of a specific category
IEnumerable<XElement> GetSettingsItems(XElement category, XDocument doc)
{
	if(doc == null) return null;
	return doc.Descendants("SettingsCategory").Where(c => 
	c.Attribute(KEY_ATTRIBUTE).Value.Equals
	(category.Attribute(KEY_ATTRIBUTE).Value)).Descendants();
}

//get all the settings items within the xml file
IEnumerable<XElement> GetAllSettingsItems(XDocument doc)
{
	if(doc == null) return null;
	var categories = GetCatregories(doc);
	List<XElement> list = new List<XElement>();
	foreach(var category in categories)
	{
		list.AddRange(category.Descendants());
	}
	return list;
}

...

这些模板在列出设置的 XML 文件和生成的代码之间起着连接作用。 一旦这些模板到位,创建其余模板就像基本的 T4 编码一样简单。

AppSettingsModel.tt 模板生成如上所述的开发中心文章中描述的 ViewModel。 它主要循环访问 XML 文件中的所有项目并生成代码。 请注意,生成的类是 partial,这允许您将自定义属性添加到 ViewModel。

...

<#@ include file="XDocumentParser.tt" #>
using System.IO.IsolatedStorage;

namespace <#= GetSettingsScreen(Parameter).Attribute(NAMESPACE_ATTRIBUTE).Value #>
{
    public partial class AppSettings
    {
		private readonly IsolatedStorageSettings _settings;

		#region key name and default value of the application settings
		<# 
		IEnumerable<XElement> items = GetAllSettingsItems(Parameter);
		foreach (var item in items) { 
		#>
		private const string <#= item.Attribute(KEY_ATTRIBUTE).Value #>Key = 
		    "<#= GetSettingsScreen(Parameter).Attribute
		    (NAMESPACE_ATTRIBUTE).Value #>.<#= item.Attribute(KEY_ATTRIBUTE).Value #>";
		private const <#= GetItemType(item) #> 
		<#= item.Attribute(KEY_ATTRIBUTE).Value #>DefaultValue = 
		    <# if(GetItemType(item).Equals("string")) 
		    { #>"<# } #><#= item.Attribute
		    (DEFAULTVALUE_ATTRIBUTE).Value #><# if(GetItemType
		    (item).Equals("string")) { #>"<# } #>;
		<# } #>
		#endregion 

...

		#region public members
		<# 
		foreach (var item in items) { 
		#>

		/// <summary>
		/// Gets or sets the settings value for <#= item.Attribute(KEY_ATTRIBUTE).Value #>.
		/// </summary>
		public <#= GetItemType(item) #> <#= item.Attribute(KEY_ATTRIBUTE).Value #>
		{
            get
            {
                return GetValueOrDefault(<#= item.Attribute(KEY_ATTRIBUTE).Value #>Key, 
                    <#= item.Attribute(KEY_ATTRIBUTE).Value #>DefaultValue);
            }
            set
            {
                if (AddOrUpdateValue(<#= item.Attribute(KEY_ATTRIBUTE).Value #>Key, value))
                {
                    Save();
                }
            }
        }
		<# } #>
		#endregion

...

<#+
XDocument Parameter
{
	get{ return (XDocument)CallContext.GetData("T4Settings.Parameter"); }
}
#>

视图及其代码隐藏也相当简单。 代码隐藏将生成的 ViewModel 设置为其 DataContext

...

<#@ include file="XDocumentParser.tt" #>
namespace <#= GetSettingsScreen(Parameter).Attribute(NAMESPACE_ATTRIBUTE).Value #>
{
    public partial class SettingsMainPage
    {
		#region class construction
        public SettingsMainPage()
        {
            InitializeComponent();
			DataContext = new AppSettings();
        }
		#endregion
    }
}

...

XAML 文件的模板循环访问类别并生成枢轴项(如果使用子页面,则生成可点击的列表项),并根据每个类别类型列出设置

...
<#@ include file="XDocumentParser.tt" #>

...

    <Grid x:Name="LayoutRoot" Background="Transparent">
		<phone:Pivot Title="<#= GetSettingsScreen
		(Parameter).Attribute(TITLE_ATTRIBUTE).Value #>">
        <#
		IEnumerable<XElement> categories = GetCatregories(Parameter);
		foreach (var category in categories) { 
		#>
			<phone:PivotItem Header="<#= 
			category.Attribute(TITLE_ATTRIBUTE).Value #>">
				<StackPanel>
				<TextBlock Text="<#= category.Attribute
				(SUMMARY_ATTRIBUTE).Value #>" Margin="15,0,0,10" 
				TextWrapping="Wrap"/>
				<#
				IEnumerable<XElement> items = GetSettingsItems(category, Parameter);
				foreach (var item in items) {

				...
	
				<# if(item.Name.ToString().Equals(TOGGLESWITCH_ITEM)) { #>
				<Grid Margin="{StaticResource PhoneTouchTargetOverhang}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <toolkit:ToggleSwitch
                        x:Name="<#= item.Attribute(KEY_ATTRIBUTE).Value #>"
						Grid.Row="0" 
                        Header="<#= item.Attribute(TITLE_ATTRIBUTE).Value #>"
                        IsChecked="{Binding <#= item.Attribute
                        (KEY_ATTRIBUTE).Value #>, Mode=TwoWay}"/>
                     <TextBlock Grid.Row="1" Margin="10,-30,0,0" 
                     Text="<#= item.Attribute(SUMMARY_ATTRIBUTE).Value #>" 
                         FontSize="{StaticResource PhoneFontSizeSmall}" 
                         TextWrapping="Wrap"/>
				</Grid>
				<# } #>

				...
	
...

结论

请注意,我没有对代码进行任何扩展的润色(因为我只是在试验 Windows Phone)。提供的解决方案只是深入研究 T4 时可以实现的目标的入口点。例如,可以包含 SinglePage 屏幕类型或其他控件,例如复选框或单选按钮。ViewModel 不是 Observable,也可以添加。

© . All rights reserved.