Sunday, March 10, 2013

Displaying Status with Live Tiles

Brain Dead Simple Windows RT App Part 4

In Windows Phone, Microsoft introduced the idea of Tiles that could provide the use with updates. Your tile is important because it is the one thing that is always there whispering to the user “run me” day and night. There are four types of updates, Local, Scheduled, Periodic and Push. Because this is a basic level intro article, I’m going to ignore Scheduled, Periodic and Push and work on Local (in the wild, you will probably need to use one of the other three).

Windows 8 offers a total of 46 tile templates that are enumerated in TileTemplateType. To update a tile you:

  1. Get the Template with TileUpdateManager.GetTemplateContent
  2. Create a notification or a new TileNotification object with a reference to the template you retrieved on Step 1 and set any ExpirationTime
  3. Set notification text on the Template XML you retrieved on Step 1
  4. Update Live Tile Create a TileUpdater and call its Update with a the TileNotification created in Step 1

Back to the App

I’m lazy, so I don’t even want to open my app to know how excited I am. I can use the apps tile to remind me. Now I only need to open the app to use the more advanced features like changing my excitement level.

NOTE: In this app, the settings roam but the Live Tile does not. Since this is a really simple demo, I’m OK with that, I would never ship an app like this.

public static void SetLiveTile(string title, string body, int? seconds = null)
{
    // Get tile template.
    var tileTemplate = TileTemplateType.TileSquareBlock;
    XmlDocument tileXml = TileUpdateManager.GetTemplateContent(tileTemplate);

    // Create notification.
    var notification = new TileNotification(tileXml);
    if (seconds.HasValue)
    {
        notification.ExpirationTime = DateTime.Now + 
        TimeSpan.FromSeconds(seconds.Value);
    }

    // Set notification text.
    XmlNodeList nodes =    tileXml.GetElementsByTagName("text");
    nodes[0].InnerText = title;
    nodes[1].InnerText = body;

    // Update Live Tile.
    var upd = TileUpdateManager.CreateTileUpdaterForApplication();
    upd.Update(notification);
}

And I add the following line to the end of saveSettings()

SetLiveTile(string.Format("{0}!", _exlamationPointCount), HelloMessage, 3600);

Complete Code


using System;
using System.ComponentModel;
using Windows.Data.Xml.Dom;
using Windows.Storage;
using Windows.UI.Notifications;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace HelloWindows
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private const string BASE_MESSAGE = "Hello Windows";

        public MainPage()
        {
            this.InitializeComponent();

            // Load Settings here:
            loadSettings();
            HelloMessage = setMessge(_exlamationPointCount);
            this.DataContext = this;
        }


        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new  PropertyChangedEventArgs(propertyName));
            }
        }

        public string HelloMessage { get; set; }


        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  
        /// The Parameter property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private int _exlamationPointCount = 0;

        private string setMessge(int exlamationPointCount)
        {
            if (exlamationPointCount > 0)
            {
                return _baseMessage + new string('!', exlamationPointCount);
            }
            else
            {
                return _baseMessage;
            }
        }

        private void addPoint_Click(object  sender, RoutedEventArgs e)
        {
            HelloMessage = setMessge(++_exlamationPointCount);
            RaisePropertyChanged("HelloMessage");
            RaisePropertyChanged("IsRemovePossible");
            // This is overkill
            saveSettings();
        }

        private void removePoint_Click(object sender, RoutedEventArgs e)
        {
            if (_exlamationPointCount >= 1)
            {
                HelloMessage = setMessge(--_exlamationPointCount);
                RaisePropertyChanged("HelloMessage");
                RaisePropertyChanged("IsRemovePossible");
                // This is overkill
                saveSettings();
            }
        }

        // Make it possible to disable Remove Point button when there
        // is nothing left to remove
        public bool IsRemovePossible
        {
            get { return _exlamationPointCount > 0; }
        }
        // I replace most refrence to BASE_MESSAGE to _baseMessage
        private string _baseMessage = BASE_MESSAGE;

        // Ensure that RoamingSettings has been loaded
        private static ApplicationDataContainer _roamingSettings;
        public static ApplicationDataContainer RoamingSettings
        {
            get
            {
              if (_roamingSettings == null)
              {
                  // If I don't want roaming, use 
                  // ApplicationData.Current.LocalSettings;
                  _roamingSettings = ApplicationData.Current.RoamingSettings;
              }
              return _roamingSettings;
            }
        }

        // I know that saving every time the value is changed,
        // in real life I'd save the data in App.OnSuspending()
        private void saveSettings()
        {
            // I combine both values into a ApplicationDataCompositeValue
            // and save them both to "HighPriority"
            var composite = new ApplicationDataCompositeValue();
            composite["baseMessage"] = _baseMessage;
            composite["exlamationPointCount"] = _exlamationPointCount;
            RoamingSettings.Values["HighPriority"] = composite;

           SetLiveTile(string.Format("{0}!" , _exlamationPointCount),
           HelloMessage, 3600);
        }
        // To be called from the constructor
        private void loadSettings()
        {
            // I get "HighPriority" cast it to ApplicationDataCompositeValue
            // and extract the values.
            var composite = (ApplicationDataCompositeValue)
            RoamingSettings.Values["HighPriority"];
            if (composite != null) // the first time it will be null
            {
                _baseMessage = (string)composite["baseMessage"];
                _exlamationPointCount = (int)composite["exlamationPointCount"];
          }
      }


      public static void SetLiveTile(string     title, string body, int? seconds = null)
      {
          // Get tile template.
          var tileTemplate = TileTemplateType.TileSquareBlock;
          XmlDocument tileXml = TileUpdateManager.GetTemplateContent(tileTemplate);

          // Create notification.
          var notification = new TileNotification(tileXml);
          if (seconds.HasValue)
          {
              notification.ExpirationTime = DateTime.Now +
              TimeSpan.FromSeconds(seconds.Value);
          }

          // Set notification text.
          XmlNodeList nodes = tileXml.GetElementsByTagName("text");
          nodes[0].InnerText = title;
          nodes[1].InnerText = body;

          // Update Live Tile.
          var upd = TileUpdateManager.CreateTileUpdaterForApplication();
          upd.Update(notification);
      }
    }
}

My Brain Dead Simple Windows RT App: The Series

No comments: