Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Windows Phone Settings Page Generation with T4

0.00/5 (No votes)
12 Nov 2013 1  
An elegant solution for standardized and rapid settings page generation for Windows Phone

Introduction

Settings pages are something almost every mobile phone application has to provide. I am experimenting with the Windows Phone 8 SDK and found several good articles explaining how to create settings pages for Windows Phone (the one from the Dev Center is quite helpful). I soon got tired of this approach as the application I am experimenting with has tons of user settings. Therefore, I was wondering whether these lines of codes could be generated using some code generation tool such as T4. The idea is to provide an XML file defining the list of settings and some T4 that generates the Views and Models.

For example, something like this:

<?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>

Will end up in something like this:

Background

This solution makes extensive use of Oleg Sych’s article on how to generate multiple outputs from single T4 template (http://www.olegsych.com/2008/03/how-to-generate-multiple-outputs-from-single-t4-template/).

Using the Code

The code is mainly composed of a set of T4s: one per output file. Just add the provided code into your Windows Phone solution and let T4 do its wonders.

Take care to remove the Custom Tool property of every T4 but Settings.tt in order to avoid unnecessary code generation.

SettingsScreen Node

The SettingsScreen node has the following mandatory attributes:

  • Namespace: identifies the name space of the generated code
  • Type: identifies the type of the settings screen (in the attached code Pivot and SubPages are supported)
  • Title: The title of the screen. Note that binding is supported.

SettingsCategory node

The SettingsCategory node has the following mandatory attributes:

  • Key: identifies the category
  • Title: The title of the category. Note that binding is supported.
  • Summary: The summary of the category. Note that binding is supported.

Settings Items

The following settings items are supported by the attached code:

  • ToggelSwitchItem (in the Windows Phone Toolkit)
  • TextBoxItem
  • SmallTextBoxItem (same as textBox but holds on one line)
  • ListItem (in the Windows Phone Toolkit)

The common and mandatory attributes are as follows:

  • Key: identifies the item
  • Title: The title of the item. Note that binding is supported.
  • Summary: The summary of the item. Note that binding is supported.
  • DefaultValue: The default value.

Any item can take the following non-mandatory attributes:

  • Dependency: Takes as a value the key of any ToggleSwitchItem. A dependent Item is disabled when the specified ToggleSwitch is OFF.
  • DependencyIndent: If the value is set to true, then the item is indented.

The ListItem takes an additional mandatory attribute: ItemsSource. Again, binding is supported.

Digging Deeper 

The main template is the Settings.tt doing the orchestration. The XML file is parsed, its content stored and made accessible via remoting CallContext. Finally the different templates are processed and their output files generated. 

...

//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");
}

...

There’s another important template providing access to the elements within the XML file: XDocumentParser.tt. This template also provides some commonly used constants such as node and attribute names.

...

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;
}

...

These templates are doing the plumbing between the XML file listing the settings and the generated code. Once these templates are in place, the creating of the remaining templates is as simple as basic T4 coding.

The AppSettingsModel.tt template is generating the ViewModel as described in the Dev Center Article mentioned above. It mainly loops over all the items within the XML file and generates the code. Note that the generated class is partial which allows you to add your custom properties to the 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"); }
}
#>

The view and its code behind are rather straightforward as well. The code behind sets the generated ViewModel as its 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
    }
}

...

The template for the XAML file loops over the categories and generates pivot items (or tapable list items if sub pages are used) and lists the settings of each category according to its type:

...
<#@ 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>
				<# } #>

				...
	
...

Conclusion

Please note that I did not polish the code to any extend (as I am only experimenting with Windows Phone). The provided solution is but an entry point to what could be achieved when digging deeper into T4. For example, one could include a SinglePage screen type or other controls such as checkboxes or radio buttons. The ViewModel is not Observable which is something that could be added, too.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here