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.

High Performance Touch Interface #wpdev #wp7dev

Every now and again i come across developer questions like why is manipulation delta slow etc. Let me tell you why.

UIElement Silverlight for #windowsphone exposes a few events like

http://msdn.microsoft.com/en-us/library/system.windows.uielement(v=vs.95).aspx

Public eventSupported by Silverlight for Windows Phone ManipulationCompleted Occurs when a manipulation and inertia on the UIElement is complete.
Public eventSupported by Silverlight for Windows Phone ManipulationDelta Occurs when the input device changes position during a manipulation.
Public eventSupported by Silverlight for Windows Phone ManipulationStarted Occurs when an input device begins a manipulation on the UIElement.

These are high level touch interfaces and there is a significant overhead in reporting (and hence delay etc).

If you are say drawing on a bitmap or canvas, you dont want a min delta change before event is fired. For that lets look at low-level interface exposed in Silverlight.

http://msdn.microsoft.com/en-us/library/system.windows.input.touch(v=vs.95).aspx

Public eventStatic memberSupported by Silverlight for Windows Phone FrameReported Occurs when the input system promotes a Windows 7 touch message for Silverlight.

Touh.FrameReported is a low level method and has little overhead and is very very precise. Let me give you a code sample

Touch.FrameReported += Touch_FrameReported;

you can do above in Loaded event. Here’s the implementation of the Touch_FrameReported handler. WorkArea is Canvas in this. I have also used this in conjugation with WritableBitmap

private void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
    try
    {
        // Determine if finger / mouse is down
        point = e.GetPrimaryTouchPoint(this.workArea);
        
        if (point.Position.X < 0 || point.Position.Y < 0)
        return;
        
        if (point.Position.X > this.workArea.Width || point.Position.Y > this.workArea.Height)
            return;
        
        if (this.lbLetter.SelectedIndex == -1)
            return;
        
        switch (point.Action)
        {
            case TouchAction.Down:
                draw = true;
                old_point = point;
                goto default;
        
            case TouchAction.Up:
                draw = false;
                break;
        
            default:
                Draw();
                break;
        }
    }
    catch
    {
        MessageBox.Show("Application encountered error processing last request.");
    }
}

I hope this is useful to #windowsphone developers out there.

Alarm Clock – Multi alarm supported added #WP #WindowsPhone

a few users asked me to provide my alarm clock app http://windowsphone.com/s?appid=f42310b4-6424-4823-85a3-6dccb1a968c3 While it was not difficult it was a slightly complicated exercise that involved issues with user display and consuming underlying apis. Now i am storing user created alarm data by itself and using that to create underlying sytem alarm notifications.

I only have to finish localisation of the app since i hardcoded all the strings i needed. Will try to finish it off today evening

Cool Camera #WP #WindowsPhone @CoolCameraWP

Cool Camera has been udpated to 1.9. I just finished uploading the new XAP to marketplace. So whats changed ?
Well not much 🙂 I have been busy with DIY at home so this is a bug fix / UX enhancement release

1) Portrait pictures with zoom were not correctly cropped. Fixed
2) Reduce album view so show only 8 but larger thumbnails
3) Metro UX enhancement in Media viewer based on Dave Crawford’s suggestions. Hide instead of disabling controls and ensure fonts are readable 🙂

I’ve also been testing my Alarm Clock app (supporting multiple alarms)

Navigate to selected Pivot Item #wpdev #wp7dev

I have come across a few instances where its desireable to navigate to a certain pivot item rather than landing on default and then finding your way around.

This is very easy. I tend to define some mechanism e.g. an enum for each pivot item. I create a static property (or you can choose whatever data passing mechanism you prefer). I set the property and navigate to pivot page and navigate to correct item.

Here’s how i do it. Itercept the OnNavigatedTo of the Pivot page and set the correct item as selected.

public enum PivotDef
{
   One,
   Two,
   Three,
   Four,
}

public static PivotDef SelectedPivot;

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
   switch (SelectedPivot)
   {
      case PivotDef.One:
         this.pvtControl.SelectedItem = this.pvt1;
         break;

      case PivotDef.Two:
         this.pvtControl.SelectedItem = this.pvt2;
         break;

      case PivotDef.Three:
         this.pvtControl.SelectedItem = this.pvt3;
         break;

      case PivotDef.Four:
         this.pvtControl.SelectedItem = this.pvt4;
         break;
   }

   base.OnNavigatedTo(e);
}

sample project is available from http://wp7pivottest.codeplex.com

Lighten Darken Skin tone in C# and Silverlight for #windowsphone #wpdev

I started Cool Camera as a stop gap application trying to figure my way around SatNav app (which started while i was answering some posts on AppHub forums). Current availble version stands at 1.6 and 1.7 is with Microsoft.

It has come a long way since 1.0 – which only supported: Taking pictures and a camera style HUD. The picture viewer was very basic. Just display the image in Image control. Since then i have * added support for filters, added video recording and playback, added album viewer. I have worked a bit more on image processing and i am a bit better at image processing.

The first set of filters were added to app thanks to René Schulte – http://kodierer.blogspot.co.uk/. I remember coming across http://picfx.codeplex.com a while back and it provided very handy way to creating and applying effects to WriteableBitmaps. I used the few supplied to get stared however before long i was asked if i could provide a way of making images darker. The most common scenario is when you use flash and the images are too white – especially faces. As i started, i remembered face detection post by Rene. http://channel9.msdn.com/coding4fun/articles/FaceLight–Silverlight-4-Real-Time-Face-Detection.

I started with Rene’s YCbCr code and the first pass to detect whether color falls into skin tone range. The first pass for skin tone detection worked just fine, how search began on how to increase or decrease luminance of image. I came across HSLColor which had an ligthen / darken method but that did’t work so eventually, i used Lerp

public int[] Process(int[] inputPixels, int width, int height)
      {
          var resultPixels = new int[inputPixels.Length];

          // Threshold every pixel
          for (int i = 0; i < inputPixels.Length; i++)
          {
              int c = inputPixels[i];

              var ycbcr = YCbCrColor.FromArgbColori(c);
              if (ycbcr.Y >= LowerThreshold.Y && ycbcr.Y <= UpperThreshold.Y
               && ycbcr.Cb >= LowerThreshold.Cb && ycbcr.Cb <= UpperThreshold.Cb
               && ycbcr.Cr >= LowerThreshold.Cr && ycbcr.Cr <= UpperThreshold.Cr)
              {
                  // skin tone match 
                  System.Windows.Media.Color sc = System.Windows.Media.Color.FromArgb((byte)(c >> 24), (byte)(c >> 16), (byte)(c >> 8), (byte)c);

                  Microsoft.Xna.Framework.Color xc = new Microsoft.Xna.Framework.Color(sc.R, sc.G, sc.B, sc.A);
                  xc = Color.Lerp(xc, Color, Amout);

                  c = (255 << 24) | ((byte)(xc.R > 255 ? 255 : xc.R) << 16) | ((byte)(xc.G > 255 ? 255 : xc.G) << 8) | (byte)(xc.B > 255 ? 255 : xc.B);
              }
              
              resultPixels[i] = c;
          }

          return resultPixels;
      }

Now all you need to do is pass the amount to Lerp and the color. To Darken you pass Black, to lighten, you pass White.

Been keeping busy with #windowsphone #wp7dev

Go a2b still in beta at version 0.5
I have been spending a bit of time on Cool Camera. Version 1.5 is going to be out soon. Version 1.6 is almost ready. I have to start localisation soon.
So far new features in 1.6 include
* HSL (Hue, Saturation and luminance) Dynamic filter
* YUV (Actually YCbCr) – Brightness, Blue and Red chroma dynamic filter
* Corrected preview of Pixelate & Hexagoneal pixelate
* Upload of photos and videos to SkyDrive
* Since album for both photos and videos.

I will post some code on SkyDrive soon