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:
- 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 - Storage:
- IsolatedStorageFile
- IsolatedStorageSettings
- 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.
- Playing Sounds
- Behaviours:
- 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.
![]() |
|
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); } } |
|
![]() |
|
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); } } |
![]() |
Silverlight | MediaElement |
XNA | MediaPlayerSoundEffects | |
![]() |
XAML | MediaElement |
MonoGame (XNA) | MediaPlayerSoundEffects | |
SharpDX | Managed DirectX wrappers for C# |
No built in support for behaviours (unlike WP Blend support through Interactions lib)
http://winrtbehaviors.codeplex.com provides DragFlickBehavior
![]() |
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.