Semantic zoom – beyond the stock behaviour – #windev #winrt

Last year I was working on Cineworld app for Windows 8. Having use LongListSelector I wanted to ensure that I can transfer the look and feel. However I was fighting multiple battles – the first one is that while LongListSelector is a single control, SemanticZoom control itself contains ZoomInView and ZoomedOutView. These are independent of each other and most examples then to show settings data to zoomed in view and then zoomed out view referring to zoomed in view. It gets messy – maybe I am just slow but it was not intuitive and took me ages.

While mucking around, I came across this post and from there, kept trying till I got what I wanted.

LLS1LLS 

Screenshot (220)

Screenshot (221)

 

<Page.Resources>
    <CollectionViewSource x:Name="cvsFilms" IsSourceGrouped="true" />

    <!-- Zoomed out templates and selectors -->
    <DataTemplate x:Key="GroupTemplate">
        <Border Margin="6" Width="75" Height="75" Background="Black" >
            <TextBlock Text="{Binding Name}" Foreground="White" FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </DataTemplate>
    <DataTemplate x:Key="EmptyGroupTemplate">
        <Border Margin="6" Width="75" Height="75" BorderBrush="Black" BorderThickness="1" Background="LightGray">
            <TextBlock Text="{Binding Name}" Foreground="Black" FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </DataTemplate>
    <local:EmptyOrFullSelector
            x:Key="FilmGroupEmptyOrFullSelector"
            Empty="{StaticResource EmptyGroupTemplate}"
            Full="{StaticResource GroupTemplate}" />
    <DataTemplate x:Key="GroupHeaderTemplate">
        <Border Margin="20, 20, 0, 0" Width="75" Height="75" Background="Black" Tapped="Grid_Tapped">
            <TextBlock Text="{Binding Key}" Foreground="White" FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </DataTemplate>
    <DataTemplate x:Key="GroupItemTemplate">
        <Border Margin="14, 20, 0, 0">
            <Grid Height="350" Width="185" Margin="0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <Image  Source="{Binding PosterUrl}" Height="278" Width="185" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="UniformToFill"/>
                <TextBlock Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Top" Text="{Binding Title}" HorizontalAlignment="Center" FontFamily="Segoe UI" />
            </Grid>
        </Border>
    </DataTemplate>
</Page.Resources>

<SemanticZoom x:Name="semanticZoom" Background="White" HorizontalAlignment="Stretch" Grid.Row="1" VerticalAlignment="Stretch">
    <SemanticZoom.ZoomedOutView>
        <GridView ScrollViewer.IsHorizontalScrollChainingEnabled="False" HorizontalAlignment="Center" ItemTemplateSelector="{StaticResource FilmGroupEmptyOrFullSelector}" >
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapGrid Background="LightGray" MaximumRowsOrColumns="9" VerticalChildrenAlignment="Center" Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
        </GridView>
    </SemanticZoom.ZoomedOutView>
    <SemanticZoom.ZoomedInView>
        <GridView x:Name="gvZoomedInFilms" ItemsSource="{Binding Source={StaticResource cvsFilms}}" IsSwipeEnabled="True" ScrollViewer.IsHorizontalScrollChainingEnabled="False" ItemTemplate="{StaticResource GroupItemTemplate}">
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
            <GridView.GroupStyle>
                <GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}">
                    <GroupStyle.Panel>
                        <ItemsPanelTemplate>
                            <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,50,0"/>
                        </ItemsPanelTemplate>
                    </GroupStyle.Panel>
                </GroupStyle>
            </GridView.GroupStyle>
        </GridView>
    </SemanticZoom.ZoomedInView>
</SemanticZoom>
List<GroupInfoList<object>> dataLetter = null;

public ListFilms()
{
    this.InitializeComponent();

    FilmData cd = new FilmData(App.Films);

    dataLetter = cd.GroupsByLetter; 
    cvsFilms.Source = dataLetter;
    gvZoomedInFilms.SelectionChanged -= gvZoomedIn_SelectionChanged;
    gvZoomedInFilms.SelectedItem = null;
    (semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = cd.FilmHeaders;     gvZoomedInFilms.SelectionChanged += gvZoomedIn_SelectionChanged;

    semanticZoom.ViewChangeStarted -= semanticZoom_ViewChangeStarted;
    semanticZoom.ViewChangeStarted += semanticZoom_ViewChangeStarted;
}

void semanticZoom_ViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e)
{
    if (e.SourceItem == null)
        return;

    if (e.SourceItem.Item.GetType() == typeof(HeaderItem))
    {
        HeaderItem hi = (HeaderItem)e.SourceItem.Item;

        var group = dataLetter.Find(d => ((char)d.Key) == hi.Name);
        if (group != null)
            e.DestinationItem = new SemanticZoomLocation() { Item = group };
    }
}

// FilmData looks like this
private List<GroupInfoList<object>> groupsByLetter = null;

public List<GroupInfoList<object>> GroupsByLetter
{
    get
    {
        if (groupsByLetter == null)
        {
            groupsByLetter = new List<GroupInfoList<object>>();

            var query = from item in Collection
                        orderby ((FilmInfo)item).Title
                        group item by ((FilmInfo)item).HeaderChar into g
                        select new { GroupName = g.Key, Items = g };

            foreach (var g in query)
            {
                GroupInfoList<object> info = new GroupInfoList<object>();
                info.Key = g.GroupName;
                foreach (var item in g.Items)
                {
                    info.Add(item);
                }
                groupsByLetter.Add(info);
            }
        }

        return groupsByLetter;
    }
}

List<HeaderItem> filmHeaders = null;
public List<HeaderItem> FilmHeaders
{
    get
    {
        if (filmHeaders == null)
        {
            filmHeaders = new List<HeaderItem>();

            char c = '#';

            filmHeaders.Add(new HeaderItem() { Name = '#', IsEnabled = this.GroupsByLetter.Exists(k => ((char)k.Key) == c) });
                    
            for (int i = 65; i <= 90; i++)
            {
                c = (char)i;

                if (this.GroupsByLetter.Exists(k => ((char)k.Key) == c))
                    filmHeaders.Add(new HeaderItem() { Name = c, IsEnabled = true });
                else
                    filmHeaders.Add(new HeaderItem() { Name = c, IsEnabled = false });
            }                    
         }

        return filmHeaders;
    }
}

I think that’s about it. Its been 6 months now and I think I am going to change how it looks :) some stuff will remain as it is, others will have to change

Background Audio #win8dev #winrt

In this post I am going to detail some of the work I have been doing on Background Audio. But before I get onto it, let me say that the last few months have been crazy / busy, not highly motivational for personal dev etc. I have done a few updates over the last few months but no new development until this month.

About 2 weeks back I started work on Porting Slydr code to WPF control and that was a breeze. Infact last night I saw something about a simple app being tested.. makes me think on whether I should beat them to it :) or not.

Now back to Background Audio. With Windows Phone, one had to create a Background Audio Player which the app and system would then communicate with. The app could kick the Background Audio Player off and then exit and the audio would keep on playing. I have talked about this before and will saying it again. WPDev 7x provided many ways of playing audio. With Windows 8 and #WinRT your options are restricted. I have gotten used to giving MediaElement more usage and credit for what it does.

<MediaElement x:Name="mePlayer" />

All you need to do is set the Source property and then call Play(). The audio starts playing. If the app gets suspended, the music stops playing. To enable background audio there are a number of things you need to do.
* Firstly set MediaElement‘s AudioCategory to BackgroundCapableMedia. This still doesn’t activate the Background Audio as its only the first step

* Now open package.appmanifest file. Open the Declarations tab and add Background Tasks. While you are there set the Supported task types property to Audio and Control Channel. You will also need to set the Start page in App settings. Of course now that you have done this, you will have to create a Badge logo before you can save the changes to appmanifest.
appManifest

* Now back to codebehind of your page and add event handlers for MediaControl‘s PlayPressed, PausePressed, StopPressed and PlayPauseTogglePressed events

In my app I also support Previous Track / Next Track so I additionally added support for PreviousTrackPressed and NextTrackPressed events.

MediaControl.PlayPressed += MediaControl_PlayPressed;
MediaControl.PausePressed += MediaControl_StopPressed;
MediaControl.StopPressed += MediaControl_StopPressed;
MediaControl.PlayPauseTogglePressed += MediaControl_PlayPauseTogglePressed;
MediaControl.NextTrackPressed += MediaControl_NextTrackPressed;
MediaControl.PreviousTrackPressed += MediaControl_PreviousTrackPressed;

and

void MediaControl_PreviousTrackPressed(object sender, object e)
{
    this.Previous();
}

void MediaControl_NextTrackPressed(object sender, object e)
{
    this.Next();
}

void MediaControl_PlayPauseTogglePressed(object sender, object e)
{
    if (MediaControl.IsPlaying)
        this.Stop();
    else
        this.Play();
}

void MediaControl_StopPressed(object sender, object e)
{
    this.Stop();
}

void MediaControl_PlayPressed(object sender, object e)
{
    this.Play();
}

Now when you run the app, initiate the audio playback and navigate away from the app.. Bingo. the audio is still playing.

Quirk: App invocation from secondary tile with app currently running #win8dev

My cineworld app for Windows 8 is now in Store and thanks to many of my dev friends, I am getting bug reports and feature requests.

Anthony Wieser just found another bug for me. The app is running and you have two cinemas in pinned state: Enfield and Cambridge. Hit Start and open one of the pinned cinema, the app shows the view you left it at. No navigation to desired cinema.

To resolve this issue, I set the app to debug and then opened App.xaml.cs and as I was putting a break point in OnActivatedMethod, I saw this

// Do not repeat app initialization when already running, just ensure that
// the window is active
if (args.PreviousExecutionState == ApplicationExecutionState.Running)
{
   Window.Current.Activate();
   return;
}

var rootFrame = new Frame();
SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

if (rootFrame.Content == null)
{
... // so on and so forth
}

this is the culprit. What I should be doing is checking params passed and then showing the correct xaml page. time to fix it now.

Update: Fixed. here’s what I have done :)

if (args.PreviousExecutionState == ApplicationExecutionState.Running)
{
    if (iCin == int.MinValue)
    {
        Window.Current.Activate();
        return;
    }
    else
        rootFrame = (Frame)Window.Current.Content;
}
else
{
    // Create a Frame to act as the navigation context and associate it with
    // a SuspensionManager key
    rootFrame = new Frame();
    SuspensionManager.RegisterFrame(rootFrame, "AppFrame");
}

if (rootFrame.Content == null || iCin != int.MinValue) // the iCin contain value passed in activation param
{
... // so on and so forth
}

once you do this, you can handle navigation params and navigate to correct xaml page.

Implement Search Contract the easy way #win8dev

Most apps contain some sort of data that you as a developer would like exposed. That’s were the Search Contract comes into play. Say the user is on the start screen and wishes to search your app, they could just start typing and select your app from the list. If you implemented Search contract, your app will be listed in the app list and the user could just type away and hit enter to see results. That would be nice wouldn’t it ?

Lets get on with it and add search contract in a simple / easy way.
Right click on the Win8 project and select Search Contract. Name it whatever you wish and hit enter.

This does two things:

  • Adds override for OnSearchActivated in your App.xaml.cs
  • Adds required page for search and display of results.

This is what the OnSearchActivated looks like

protected async override void OnSearchActivated(Windows.ApplicationModel.Activation.SearchActivatedEventArgs args)
{
    // TODO: Register the Windows.ApplicationModel.Search.SearchPane.GetForCurrentView().QuerySubmitted

    // event in OnWindowCreated to speed up searches once the application is already running

    // If the Window isn't already using Frame navigation, insert our own Frame
    var previousContent = Window.Current.Content;
    var frame = previousContent as Frame;

    // If the app does not contain a top-level frame, it is possible that this 
    // is the initial launch of the app. Typically this method and OnLaunched 
    // in App.xaml.cs can call a common method.
    if (frame == null)
    {
        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key
        frame = new Frame();
        Cineworld.Common.SuspensionManager.RegisterFrame(frame, "AppFrame");

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // Restore the saved session state only when appropriate
            try
            {
                await Cineworld.Common.SuspensionManager.RestoreAsync();
            }
            catch //(Cineworld.Common.SuspensionManagerException)
            {
                //Something went wrong restoring state.
                //Assume there is no state and continue
            }
        }
    }

    frame.Navigate(typeof(SearchResults), args.QueryText);
    Window.Current.Content = frame;

    // Ensure the current window is active
    Window.Current.Activate();
}

as you can see it is passing the search query to the Search Results page while navigating.
Now lets look at the code-behind in SearchResults.xaml.
FYI, I am loading my dataset if required in the OnNavigatedTo method. This page is a simple results display page. It contains GridView / ListView to display results. Lets look at the LoadState method

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var queryText = navigationParameter as String;

    List<SearchResult> matches = new List<SearchResult>();

    foreach (var f in App.Films.Values)
    {
        IEnumerable<CastInfo> casts = from cast in f.FilmCast
                                        where cast.Name.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)
                                        select cast;

        foreach (var c in casts)
        {
            //searchCinemas.Add(c.Name, c);
            matches.Add(new SearchResult() { Name = c.Name, Subtitle = String.Format("{0} in {1}", c.Character, f.Title), SearchObject = f, Image = c.ProfilePath });
        }
    }

    IEnumerable<FilmInfo> films = from film in App.Films.Values
                                    where film.Title.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)
                                    select film;

    searchFilms.Clear();
    searchCinemas.Clear();

    foreach (var f in films)
    {
        searchFilms.Add(f.Title, f);
        matches.Add(new SearchResult() { Name = f.Title, Image = f.PosterUrl, SearchObject = f });
    }

    IEnumerable<CinemaInfo> cinemas = from cinema in App.Cinemas.Values
                                        where cinema.Name.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)
                                        select cinema;

    foreach (var c in cinemas)
    {
        searchCinemas.Add(c.Name, c);
        matches.Add(new SearchResult() { Name = c.Name, SearchObject = c, Image = new Uri("ms-appx:///Assets/Background.png") });
    }
            
    // TODO: Application-specific searching logic.  The search process is responsible for
    //       creating a list of user-selectable result categories:
    //
    //       filterList.Add(new Filter("<filter name>", <result count>));
    //
    //       Only the first filter, typically "All", should pass true as a third argument in
    //       order to start in an active state.  Results for the active filter are provided
    //       in Filter_SelectionChanged below.

    // Communicate results through the view model
    this.DefaultViewModel["QueryText"] = '\u201c' + queryText + '\u201d';

    bAllowNav = false;

    this.DefaultViewModel["Results"] = matches;

    bAllowNav = true;

    var filterList = new List<Filter>();
    filterList.Add(new Filter("All", 0, true));

    //this.DefaultViewModel["Filters"] = filterList;
    //this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;
}

I added code to search my dataset and to add results to DefaultViewModel. I don’t use MVVM but the default code implemented basic binding etc and I thought I’d leave it at that. I accumulate the results and save them and let bindings do the rest. Let me show you how it looks
Say I got to start screen, type I ben and select my app, this is what I get.

Now you have a nice list of matched entries. Now suppose I wanted to let users click on a matched entry and to navigate them to details page ? I implemented GridView ItemClick event (SelectionChanged doesn’t work for some reason)

private void resultsGridView_ItemClick(object sender, ItemClickEventArgs e)
{
    if (e.ClickedItem != null && bAllowNav)
    {
        SearchResult res = (SearchResult)e.ClickedItem;
        if (res.SearchObject is FilmInfo)
        {
            FilmDetails.SelectedFilm = (res.SearchObject as FilmInfo);
            this.Frame.Navigate(typeof(FilmDetails));
        }
        else if (res.SearchObject is CinemaInfo)
        {
            CinemaDetails.SelectedCinema = (res.SearchObject as CinemaInfo);
            this.Frame.Navigate(typeof(CinemaDetails));
        }
    }
}

Hope this post is helpful to others looking to implement the search contract. Happy coding!

Updated PersistHelper for #Win8Dev #WinRT

A while back I posted some persistence helper class. It was hardcoded to use LocalFolder and LocalSettings. I eventually updated it. Now it requires you the developer to specify a folder and settings object. Hope it is helpful.

public class PersistHelper
{
    //private static StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.RoamingFolder;
    //private static ApplicationDataContainer storageSettings = ApplicationData.Current.LocalSettings;

    public async static Task<bool> FileExisits(StorageFolder folder, string fileName)
    {
        try
        {
            StorageFile file = await folder.GetFileAsync(fileName);
            return file != null;
        }
        catch
        {
            return false;
        }
    }

    private async static Task<StorageFile> GetFileIfExistsAsync(StorageFolder folder, string fileName)
    {
        try
        {
            return await folder.GetFileAsync(fileName);
        }
        catch
        {
            return null;
        }
    }


    public static async Task<T> LoadObjectFromStorage<T>(Windows.Storage.StorageFolder storageFolder)
    {
        T ObjToLoad = default(T);

        try
        {
            StorageFile storageFile = await storageFolder.CreateFileAsync(GetFileName<T>(), 
                CreationCollisionOption.OpenIfExists);

            using (Stream inStream = await storageFile.OpenStreamForReadAsync())
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                ObjToLoad = (T)serializer.Deserialize(inStream);
            }
        }
        catch (Exception error)
        {
            throw new NotImplementedException(error.Message);
        }

        return ObjToLoad;
    }

    public static async void SaveObjectToStorage<T>(T ObjectToSave, Windows.Storage.StorageFolder storageFolder)
    {
        string filename = GetFileName<T>();
                
        using (Stream fs = await storageFolder.OpenStreamForWriteAsync(filename, CreationCollisionOption.ReplaceExisting))
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                XmlSerializer ser = new XmlSerializer(typeof(T));
                ser.Serialize(sw, ObjectToSave);
            }
        }
    }

    public static string GetFileName<T>()
    {
        return typeof(T).FullName + ".xml";
    }

    public async static Task<bool> IsObjectPersisted<T1>(Windows.Storage.StorageFolder storageFolder)
    {
        string file = GetFileName<T1>();

        StorageFile storageFile = await GetFileIfExistsAsync(storageFolder, file);

        return (storageFile != null);
    }

    public static T LoadSetttingFromStorage<T>(string Key, Windows.Storage.ApplicationDataContainer storageSettings)
    {
        T ObjToLoad = default(T);

        if (storageSettings.Values.ContainsKey(Key))
        {
            using (StringReader sr = new StringReader((string)storageSettings.Values[Key]))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                ObjToLoad = (T)serializer.Deserialize(sr);
            }
        }

        return ObjToLoad;
    }

    public static void SaveSettingToStorage(string Key, object Setting, Windows.Storage.ApplicationDataContainer storageSettings)
    {
        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        {
            XmlSerializer ser = new XmlSerializer(Setting.GetType());
            ser.Serialize(sw, Setting);
        }

        if (!storageSettings.Values.ContainsKey(Key))
        {
            storageSettings.Values.Add(Key, sb.ToString());
        }
        else
        {
            storageSettings.Values[Key] = sb.ToString();
        }
    }

    public static bool IsSettingPersisted(string Key, Windows.Storage.ApplicationDataContainer storageSettings)
    {
        return storageSettings.Values.ContainsKey(Key);
    }
}

Embed / use custom font in Windows 8 Store apps #win8dev #winrt

I was supposed to blog earlier but forgot. Saw a question on Stackoverflow and thought.. now is the time. So how does one embed fonts ?
Well just include them in project as Content files.

How do you use them.. here is how

<ComboBox x:Name="cbFont" Width="230" Margin="0, 10, 0, 0" Foreground="Black" Background="Gray" SelectionChanged="cbFont_SelectionChanged">
     <ComboBoxItem Content="12:34" FontFamily="Assets/Fonts/akka.ttf#Akka" />
     <ComboBoxItem Content="12:34" FontFamily="Assets/Fonts/alghorie neue.ttf#alghorie neue" />
     <ComboBoxItem Content="12:34" FontFamily="Assets/Fonts/Alpha Mutation.ttf#Alpha Mutation" />
     <ComboBoxItem Content="12:34" FontFamily="Assets/Fonts/AlphaFlowers.ttf#AlphaFlowers" />
     <ComboBoxItem Content="12:34" FontFamily="Assets/Fonts/AMSTRG__.TTF#Ams Trame" />
     <ComboBoxItem Content="12:34" FontFamily="Assets/Fonts/ARCADE_I.TTF#Arcade Interlaced" />
     <ComboBoxItem Content="12:34" FontFamily="Assets/Fonts/ARCADE_R.TTF#Arcade Rounded" />
</ComboBox>

You dont have to use combo box you can use anything.. i use it with TextBlock etc as well. Just remember. After the font file name, you need to put the font name itself. Thats all

Porting Windows Phone App to Windows 8 #wpdev #win8dev

The main message of HHGTG was “Don’t Panic”. It’s universally applicable and the case for porting you WP app to Windows 8 is no different. As WP Devs, we are already used to restrictions on what can and cannot be done. Windows 8 is a different beast that uses the same idea but being a full-blown OS, APIs exposed are different.

Windows Phone API is Silverlight API. Windows 8 has native support for XAML & HTML5. The API available to developers are WinRT and can be used from C++/CX, .NET and JS. I am going to discuss porting a C# Silverlight Windows Phone App to Windows 8. There are two parts to porting story. Get the code working and adapting the user interface. Getting the code working is the easy part. Adapting the UI slightly a bit tricky. Let’s get started

Code Porting – the easy way

Like many, I prefer to do just about everything the easy way. How do I go about it ? Well I take chunks of code and I move it between projects.

1) Custom Controls:

If you use custom controls, you might have to port some of them. A lot of controls have equivalents. If you use ListPicker or LongListSelector from Silverlight Toolkit, try ComboBox. I used AutoCompleteBox and had to write my own. If source is available, porting control is easy. In another project, I ported coding 4 fun toolkits Hexagon Color Picker.

2) XAML:

Start with the basic grid and build your xaml by pulling chunks from Windows Phone project. Most xaml with exception of certain controls can be copied. I have migrated storyboard animations etc no issues.

3) C# code:

    1. WinRT development like WP Dev requires use of asynchronous mechanism however the native WinRT API prefers to use async Task mechanism. Any methods making system calls need to be async and APIs need to be waited for.
      Read more about async programming on msdn http://msdn.microsoft.com/en-us/library/hh191443.aspx
    2. Storage:
      •   IsolatedStorageFile
      •   IsolatedStorageSettings
      public static void GetLevelData(DictionaryDef dict, int Level)
      {
          if (dict != DictionaryDef.NotSet)
          {
              App.WordList.Clear();
              App.WordDict.Clear();
      
              string File = GetLevelFile(dict, Level);
      
              Uri uriPaths = new Uri(File, UriKind.RelativeOrAbsolute);
      
              StreamResourceInfo sriPaths = System.Windows.Application.GetResourceStream(uriPaths);
      
      
              using (StreamReader sr = new StreamReader(sriPaths.Stream))
              {
                  string l;
                  while ((l = sr.ReadLine()) != null)
                  {
                      if (!String.IsNullOrWhiteSpace(l))
                      {
                          App.WordList.Add(l);
                          App.WordDict.Add(l, 0);
                      }
                  }
              }
          }
      }
      
      public class PersistHelper
      {
          public static T LoadObjectFromStorage<T>()
          {
              T ObjToLoad = default(T);
      
              try
              {
                  using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
                  {
                      if (isf.FileExists(GetFileName<T>()))
                      {
                          using (IsolatedStorageFileStream fs = isf.OpenFile(GetFileName<T>(), System.IO.FileMode.Open))
                          {
                              XmlSerializer ser = new XmlSerializer(typeof(T));
                              ObjToLoad = (T)ser.Deserialize(fs);
                          }
                      }
      
                  }
              }
              catch (Exception error)
              {
                  throw new NotImplementedException(error.Message);
              }
      
              return ObjToLoad;
          }
      
          public static void SaveObjectToStorage<T>(T ObjectToSave)
          {
              TextWriter writer;
      
              using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
              {
                  using (IsolatedStorageFileStream fs = isf.OpenFile(GetFileName<T>(), System.IO.FileMode.Create))
                  {
                      writer = new StreamWriter(fs);
                      XmlSerializer ser = new XmlSerializer(typeof(T));
                      ser.Serialize(writer, ObjectToSave);
                      writer.Close();
                  }
              }
          }
      
          public static string GetFileName<T>()
          {
              return typeof(T).FullName + ".xml";
          }
      
          public static bool IsObjectPersisted<T1>()
          {
              using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
              {
                  return isf.FileExists(GetFileName<T1>());
              }
          }
      
          public static T LoadSetttingFromStorage<T>(string Key)
          {
              T ObjToLoad = default(T);
      
              if (IsolatedStorageSettings.ApplicationSettings.Contains(Key))
              {
                  ObjToLoad = (T)IsolatedStorageSettings.ApplicationSettings[Key];
              }
      
              return ObjToLoad;
          }
      
          public static void SaveSettingToStorage(string Key, object Setting)
          {
              if (!IsolatedStorageSettings.ApplicationSettings.Contains(Key))
              {
                  IsolatedStorageSettings.ApplicationSettings.Add(Key, Setting);
              }
              else
              {
                  IsolatedStorageSettings.ApplicationSettings[Key] = Setting;
              }
          }
      
          public static bool IsSettingPersisted(string Key)
          {
              return IsolatedStorageSettings.ApplicationSettings.Contains(Key);
          }
      }
      
      •   StorageFile / StorageFolder.
      •   Get access to .NET streams using above
      •   Package.Current.InstalledLocation.Path – for   reading content files
      •   ApplicationData.Current provides LocalFolder,   RoamingFolder & TempFolder
      •   ApplicationData.Current provides LocalSettings   & RoamingSettings
      •   RoamingFolder / RoamingSettings shared between   devices.
      public static async void GetLevelData(DictionaryDef dict, int Level)
      {
          if (dict != DictionaryDef.NotSet)
          {
              App.WordDict.Clear();
      
              string File = Path.Combine(Package.Current.InstalledLocation.Path, GetLevelFile(dict, Level));
      
              StorageFolder folder = await StorageFolder.GetFolderFromPathAsync(Path.GetDirectoryName(File));
      
              using (Stream s = await folder.OpenStreamForReadAsync(Path.GetFileName(File)))
              {
                  using (StreamReader sr = new StreamReader(s))
                  {
                      string l = null;
                      while (true)
                      {
                          l = sr.ReadLine();
                          if (l == null)
                              break;
      
                          if (l.Length > 0)
                              App.WordDict.Add(l, null);
                      }
                  }
              }
          }
      }
      
      public class PersistHelper
      {
          //private static StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.RoamingFolder;
          //private static ApplicationDataContainer storageSettings = ApplicationData.Current.LocalSettings;
      
          private async static Task GetFileIfExistsAsync(StorageFolder folder, string fileName)
          {
              try
              {
                  return await folder.GetFileAsync(fileName);
      
              }
              catch
              {
                  return null;
              }
          }
      
      
          public static async Task LoadObjectFromStorage(Windows.Storage.StorageFolder storageFolder)
          {
              T ObjToLoad = default(T);
      
              try
              {
                  StorageFile storageFile = await storageFolder.CreateFileAsync(GetFileName(),
                      CreationCollisionOption.OpenIfExists);
      
                  using (Stream inStream = await storageFile.OpenStreamForReadAsync())
                  {
                      XmlSerializer serializer = new XmlSerializer(typeof(T));
                      ObjToLoad = (T)serializer.Deserialize(inStream);
                  }
              }
              catch (Exception error)
              {
                  throw new NotImplementedException(error.Message);
              }
      
              return ObjToLoad;
          }
      
          public static async void SaveObjectToStorage(T ObjectToSave, Windows.Storage.StorageFolder storageFolder)
          {
              string filename = GetFileName();
      
              using (Stream fs = await storageFolder.OpenStreamForWriteAsync(filename, CreationCollisionOption.ReplaceExisting))
              {
                  using (StreamWriter sw = new StreamWriter(fs))
                  {
                      XmlSerializer ser = new XmlSerializer(typeof(T));
                      ser.Serialize(sw, ObjectToSave);
                  }
              }
          }
      
          public static string GetFileName()
          {
              return typeof(T).FullName + ".xml";
          }
      
          public async static Task IsObjectPersisted(Windows.Storage.StorageFolder storageFolder)
          {
              string file = GetFileName();
      
              StorageFile storageFile = await GetFileIfExistsAsync(storageFolder, file);
      
              return (storageFile != null);
          }
      
          public static T LoadSetttingFromStorage(string Key, Windows.Storage.ApplicationDataContainer storageSettings)
          {
              T ObjToLoad = default(T);
      
              if (storageSettings.Values.ContainsKey(Key))
              {
                  using (StringReader sr = new StringReader((string)storageSettings.Values[Key]))
                  {
                      XmlSerializer serializer = new XmlSerializer(typeof(T));
                      ObjToLoad = (T)serializer.Deserialize(sr);
                  }
              }
      
              return ObjToLoad;
          }
      
          public static void SaveSettingToStorage(string Key, object Setting, Windows.Storage.ApplicationDataContainer storageSettings)
          {
              StringBuilder sb = new StringBuilder();
              using (StringWriter sw = new StringWriter(sb))
              {
                  XmlSerializer ser = new XmlSerializer(Setting.GetType());
                  ser.Serialize(sw, Setting);
              }
      
              if (!storageSettings.Values.ContainsKey(Key))
              {
                  storageSettings.Values.Add(Key, sb.ToString());
              }
              else
              {
                  storageSettings.Values[Key] = sb.ToString();
              }
      
          }
      
          public static bool IsSettingPersisted(string Key, Windows.Storage.ApplicationDataContainer storageSettings)
          {
              return storageSettings.Values.ContainsKey(Key);
          }
      }
      
    3. Playing Sounds
    4. Silverlight MediaElement
      XNA MediaPlayerSoundEffects
      XAML MediaElement
      MonoGame   (XNA) MediaPlayerSoundEffects
      SharpDX Managed   DirectX wrappers for C#
    5. Behaviours:
    6. No built in support for behaviours (unlike WP Blend support through Interactions lib)

      http://winrtbehaviors.codeplex.com provides DragFlickBehavior

    7. Navigation:
      • Use Frame class for navigation in WinRT. Exposes methods similar to those of NaviationService.
      • Frame does not support AddBackEntry and RemoveBackEntry
      • No Back button and associated event. Add backbutton or consume a xaml page other than Basic Page (all other pages derive from LayoutAwarePage) and a back button is added and is automatically bound to correct method.
    8. NavigationService.Navigate(new Uri("/GamePlay.xaml", UriKind.Relative));
      
      if(NavigationService.CanGoBack)
          NavigationService.GoBack();
      
      Frame.Navigate(typeof(GamePlay));
      
      if(Frame.CanGoBack)
          Frame.GoBack();
      

      Designing for Windows 8

      This is the difficult part and you get the hang of this after a few AEL sessions. Windows 8 app is more than just a port of Windows Phone app.

      1)      The screen real estate is only for your apps real consumer content.

      2)      Settings Pane is a single place to hold things relating to app configuration. App config, in app purchase into, privacy policy, about etc. all these are exposed by Settings Pane.

      3)      Use Settings Flyout (Callisto lib) to link individual item to Xaml content.. e.g. settings flyout to display app config.

      4)      Windows 8 apps do more than Portrait and Landscape modes. They have snapped and filled modes as well.

      5)      There is life outside Pivot and Panorama controls. Use FlipView, Grid, GridView, Semantic zoom etc so expose your content in the best way possible.

      6)      Take your time. Porting the 1st app is always difficult – it gets a lot easier going forward.

How to measure rendered string dimensions in #Win8Dev

While Windows 8 WinRT supports ViewBox control (its a control that allows text to auto resize depending upon available space), with my alarm clock app, I noticed substantial jittering (and resizing) as time changes between seconds. Of course you don’t want that happening.

The solution is to find optimum font size at start and then sticking with it until a resize is desired. Once you set the font family and font size to a textblock (tb), you can try getting its height / width.

With Windows Phone, the following would work well

this.tb.FontSize = 20;
double currentWidth = this.tb.ActualWidth;
double currentHeight = this.tb.ActualHeight;

This however does not work with Windows 8 / WinRT API. Here’s what you need to do

this.tb.FontSize = 20;
this.tb.Measire(new Size(400, 300)); // assuming that 400x300 is max size of textblock you want
double currentWidth = this.tb.DesiredSize.Width;
double currentHeight = this.tb.DesiredSize.Height;

Thats it really. I tend to increase / decrease font size depending upon need and only recalculated it if user wants a different font or changes the view (ie default to snapped etc)

Table Clock for Windows 8 submitted to Store

As you can see, I am getting rather comfortable with #Win8Dev. Coding now takes only a bit longer than it did for #WPDev. I ported some parts of Alarm Clock app I wrote for Windows Phone. In this case, v1 is a simple clock app that supports about 50 fonts and many UI colors. In next version I will try to add in-app purchase for Alarm support and ad removal etc.

Here’s a screencast of the app running in simulator

in app purchase with #win8dev #winrt

Windows 8 – WinRT API exposes few classes that make implementing #IAP a simple task. In additional it provides simulator class that makes testing the functionality easy.

The store related functionality is exposed by the following classes

CurrentApp class                       Defines methods and properties you can use to get license and listing info about the current app and perform in-app purchases.
CurrentAppSimulator class Defines methods and properties used to instantiate an object that you can  use to get simulated license info during testing.
LicenseInformation class Provides access to the current app’s license metadata.
ListingInformation class Provides the listing info that describes the app in the Windows Store.
ProductLicense class Provides info about a license that is associated with an in-app offer.
ProductListing class Provides localized info about an  in-app offer in your app.

To test the code in simulator, you need to load some data is. I used in-app-purchase.xml like in the store sample – it looks like this

<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
  <ListingInformation>
    <App>
      <AppId>988b90e4-5d4d-4dea-99d0-e423e414ffbc</AppId>
      <LinkUri>http://apps.microsoft.com/webpdp/app/988b90e4-5d4d-4dea-99d0-e423e414ffbc</LinkUri>
      <CurrentMarket>en-us</CurrentMarket>
      <AgeRating>3</AgeRating>
      <MarketData xml:lang="en-us">
        <Name>In-app purchases</Name>
        <Description>AppDescription</Description>
        <Price>0.00</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </App>
    <Product ProductId="SingleChar1Hint">
      <MarketData xml:lang="en-us">
        <Name>SingleChar1Hint</Name>
        <Price>0.99</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </Product>
    <Product ProductId="SingleChar2Hint">
      <MarketData xml:lang="en-us">
        <Name>SingleChar2Hint</Name>
        <Price>0.99</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </Product>
    <Product ProductId="DoubleCharHint">
      <MarketData xml:lang="en-us">
        <Name>DoubleCharHint</Name>
        <Price>1.29</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </Product>
    <Product ProductId="AdFree">
      <MarketData xml:lang="en-us">
        <Name>AdFree</Name>
        <Price>0.99</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </Product>
  </ListingInformation>
  <LicenseInformation>
    <App>
      <IsActive>true</IsActive>
      <IsTrial>false</IsTrial>
    </App>
    <Product ProductId="SingleChar1Hint">
      <IsActive>false</IsActive>
    </Product>
    <Product ProductId="SingleChar2Hint">
      <IsActive>false</IsActive>
    </Product>
    <Product ProductId="DoubleCharHint">
      <IsActive>true</IsActive>
    </Product>
    <Product ProductId="AdFree">
      <IsActive>true</IsActive>
    </Product>
  </LicenseInformation>
</CurrentApp>

In the app initialisation code, I tend to load this data into the CurrentAppSimulator.

#if DEBUG
    StorageFolder proxyDataFolder = await Package.Current.InstalledLocation.GetFolderAsync("Assets");
    StorageFile proxyFile = await proxyDataFolder.GetFileAsync("in-app-purchase.xml");
    await CurrentAppSimulator.ReloadSimulatorAsync(proxyFile);
#endif 

as you can see I use compiler preprocessing directive to make sure only debug code uses CurrentAppSimulator and the dummy IAP pricing data. Once the data is loaded, you can use CurrentAppSimulator like you CurrentApp class. Lets have a look further. I continue with compiler directives through out..
In the Page loaded event handler, I check to see if I need to show any particular IAP option

#if DEBUG
    LicenseInformation licenseInformation = CurrentAppSimulator.LicenseInformation;
    ListingInformation productListing = await CurrentAppSimulator.LoadListingInformationAsync();
#else
    LicenseInformation licenseInformation = CurrentApp.LicenseInformation;
    ListingInformation  productListing = await CurrentApp.LoadListingInformationAsync();
#endif

    var adFreeIAPListing = productListing.ProductListings["AdFree"];
    this.spAdFree.Visibility = (bAdFree ? Windows.UI.Xaml.Visibility.Collapsed : Windows.UI.Xaml.Visibility.Visible);
    this.tbAdFree.Text = String.Format("buy for {0}", adFreeIAPListing.FormattedPrice);

the buy button itself is in stackpanel called spAdFree. Lets look at the IAP handling code

#if DEBUG
    await CurrentAppSimulator.RequestProductPurchaseAsync("AdFree", false);
#else
    await CurrentApp.RequestProductPurchaseAsync("AdFree", false);
#endif
    if (AdFreeChanged != null)
        AdFreeChanged();

The AdFreeChanged in an event I expose to toggle main page’s ad control. The IAP is shown in Settings Flyout. If you have any questions, drop me a message