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






4.24/5 (7投票s)
一种用于 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
:标识设置屏幕的类型(在附带的代码中支持 Pivot 和 SubPages)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
,也可以添加。