Thursday 22 January 2015

LocalSettings

There are multiple ways to store user data in winRT apps, one option for simple data is the LocalSettings, now the local settings can be simply used as a key value Collection, but that's rather boring, this is because you could in essence use a Resource dictionary for the same thing.

What's interesting about the LocalSettings date Container is that it can contain sub containers, so for example you could have multiple containers in the LocalSettings container that all have the same sub valuekey pairs, what i mean is you could have something like the following
  • PersonOne
    • FirstName
    • LastName
    • BirthDate
  • PersonTwo
    • FirstName
    • LastName
    • BirthDate
  • PersonThree
    • FirstName
    • LastName
    • BirthDate
  • etc

ok lets look at an example


pretty straight forward app, now let's look at the xaml to make this happen
<Page
    x:Class="pc.LocalSettings.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.LocalSettings"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <Style TargetType="TextBox">
            <Setter Property="FontSize" Value="34"/>
        </Style>
        <Style TargetType="ComboBox">
            <Setter Property="FontSize" Value="34"/>
        </Style>
        <Style TargetType="DatePicker">
            <Setter Property="FontSize" Value="34"/>
        </Style>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="34"/>
            <Setter Property="Margin" Value="0 20 20 0"/>
        </Style>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="1" Text="LocalSettings Example"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   VerticalAlignment="Center"/>
        <StackPanel Grid.Column="1" Grid.Row="1" Margin="0 0 100 0">
            <ComboBox x:Name="People_CB"/>
            <TextBox x:Name="FirstName_TXT" Header="First Name:"/>
            <TextBox x:Name="LastName_TXT" Header="Last Name:"/>
            <DatePicker x:Name="BirthDate_DP" Header="Date Picker:" />
            <StackPanel Orientation="Horizontal">
                <Button x:Name="Save_BTN" Content="Save"/>
                <Button x:Name="Delete_BTN" Content="Delete"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>


easy enough, i don't think anything warrants an explanation. so let's just jump into the code-behind

using System;
using System.Collections.Generic;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace pc.LocalSettings
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            //Get the local settings
            var localSettings = ApplicationData.Current.LocalSettings;
           
            //use the key of each of the containers as an item
            //in our people combo box
            foreach (var fullName in localSettings.Containers.Keys)
                this.People_CB.Items.Add(fullName);

            //add our event revievers
            this.People_CB.SelectionChanged += People_CB_SelectionChanged;
            this.Save_BTN.Click += Save_BTN_Click;
            this.Delete_BTN.Click += Delete_BTN_Click;
        }

        //delete a contianer from our local settings
        void Delete_BTN_Click(object sender, RoutedEventArgs e)
        {
            //get the key name from the combo box
            var selectedPerson = People_CB.SelectedValue.ToString();

            //get our local settings container
            var localSettings = ApplicationData.Current.LocalSettings;
           
            //delete the container with the specified key
            localSettings.DeleteContainer(selectedPerson);

            //remove the corresponding key from the combo box
            People_CB.Items.Remove(selectedPerson);

            //clear the input values
            this.FirstName_TXT.Text = this.LastName_TXT.Text = String.Empty;
            this.BirthDate_DP.Date = DateTimeOffset.Now;
        }

        //load the data for a specific container
        void People_CB_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //prevent error when deleting a container
            if (e.AddedItems.Count == 0)
                return;

            //get contaier key to load
            var selectedPerson = e.AddedItems[0].ToString();

            //get local settings
            var localSettings = ApplicationData.Current.LocalSettings;

            //load sub container with specified key
            ApplicationDataContainer Person = localSettings.Containers[selectedPerson];

            //populate inputs with specified container values
            this.FirstName_TXT.Text = Person.Values["firstName"].ToString();
            this.LastName_TXT.Text = Person.Values["lastName"].ToString();
            this.BirthDate_DP.Date = (DateTimeOffset)Person.Values["birthDate"];
        }

        void Save_BTN_Click(object sender, RoutedEventArgs e)
        {
            //get values from input fields
            var firstName = FirstName_TXT.Text;
            var lastName = LastName_TXT.Text;
            var birthDate = BirthDate_DP.Date;

            //set key to use
            var fullName = String.Format("{0} {1}", firstName, lastName);

            //get local settings
            var localSettings = ApplicationData.Current.LocalSettings;

            //get or create the specified subcontaier
            ApplicationDataContainer Person =
                localSettings.CreateContainer(firstName, ApplicationDataCreateDisposition.Always);

            //save or update the subcontainers values
            SaveContainerData(Person, "firstName", firstName);
            SaveContainerData(Person, "lastName", lastName);
            SaveContainerData(Person, "birthDate", birthDate);

            if (!this.People_CB.Items.Contains(firstName))
                this.People_CB.Items.Add(firstName);
        }

        void SaveContainerData<T>(ApplicationDataContainer Container, string Key, T Value) {
            //check if contaier already has key, if so update it if not create it.
            if (Container.Values.ContainsKey(Key))
                Container.Values[Key] = Value;
            else
                Container.Values.Add(new KeyValuePair<string, object>(Key, Value));
        }
    }
}


the code-behind is pretty straight forward as well, the only thing really worth mentioning, is when creating a sub container that is

ApplicationDataContainer Person = localSettings.CreateContainer(firstName, ApplicationDataCreateDisposition.Always);

you have two options
Always:loads the container if it exists or creates it if it doesn't
Existing:only loads contains that exist throws and exception if you try to create a new one.