OrientationHelper for Windows Phone and Windows 8

This is a small utility to help with WP7,WP8 and Windows Store app development where there is a need to design your UX in order to support multiple screen sizes and orientations.
(Phew i almost said Metro Style..Wait..Damn it)

It is inspired by ‘LayoutAwarePage’ , an utility class included in the templates for Windows Store Apps.

On the Windows Phone side there are 6 orientation states:

  • Landscape
  • LandscapeLeft
  • LandscapeRight
  • Portrait
  • PortraitUp
  • PortraitDown

On the Windows Store side there are 4 view states :

  • Filled
  • Snapped
  • FullScreenLandscape
  • FullScreenPortrait

Before we go into details here is an example of the utility working on the Phone in Portrait mode :
On the left side we have setup our utility to automatically run some arbitrary scale animations ,defined in XAML, based on the current Orientation or ViewState. There’s nothing sophisticated in the screenshots below. Simply showing off the functionality.

Here you can see another one automatically running on the left side in Landscape mode.

Similarly on the Windows 8 side:

(FullScreenPortrait)

(FullScreenLandscape)

(Filled)

(Snapped)

What the designer needs to do is describe the UI (Xaml) for all screens and for all possible orientations/view states in order to provide a good UX. There is no clear pattern/guidelines on how this should be done although LayoutAwarePage gives us a good ‘clean’ solution by taking advantage of a well known proven XAML solution called the VisualStateManager. We are, after all, trying to tackle switching between multiple visual states so VisualStateManager sounds like a natural fit. And it is.

The basic idea is , similar to creating Custom Controls, to declare all views for all supported orientations/view states inside the same XAML file (what if the UI is composite though?) and use VisualStates, one for each specific orientation/view state, to describe the UI changes needed to transition between View designs. We will then let VisualStateManager do it’s work by asking it programmatically to switch between states when the platform notifies us of a change in orientation/view state.

What the developer needs to do is manually listen for orientation/view state changes for all visible screens
and call VisualStateManager to transition the current screen to the current view state.

This programmatic part of the developer seems like a lot of manual work that could be done automatically.
There’s gonna be a lot of code-behind spread around listening for the same event and dealing manually
with VisualState changing (or manually enabling/disabling controls, running animations etc etc if we didnt
use VisualStateManager)
Unfortunately no one can help the poor designer (developer?) that has to rewrite/redesign the whole UI
again and again. It has to happen.

LayoutAwarePage solves it by creating a Page base class for Metro style apps.
This utility is instead an attached property and supports Pages, user controls and custom controls. Haven’t made a sample with custom controls but it should be the same as user controls.
What if though i want to create a reusable custom control/user control and i want to have IT define how it should look on different orientations/view states instead of forcing the consumer/user of the control to do it.
Sometimes ,me as a user of a control, might need to tweak more stuff “inside” the control to make it look right. Lets say for example that i have a control that defines two buttons side by side ,stretched, that fill my Landscape view across nicely. I would have liked if the control itself switched them to be stacked (one on top of the other) when in Portrait mode. I can’t do that though unless the control supported that. Note that it would be best if the control supported that but didn’t enforce it.I the user would like to have control over when and if the control is allowed to auto-adapt by itself or i will solve it outside or the control. The utility supports this scenario by letting me decide whether or not to set the attached property OrientationHelper.IsActive on the control that enables it’s orientation/view state support if of course the control has defined the appropriate VisualStates.

The solution seemed transferable to Windows Phone as well and so the utility comes in 3 assemblies supporting WP7/WP8/WIN8.

What we need to do in order to use it is
1) set the attached property OrientationHelper.IsActive=”true” on a Page, UserControl or CustomControl
2) Define one VisualStateGroup on the orientation-aware control named “PageOrientationStates” for WP
and “ApplicationViewStates” for Win8.
3) Define one VisualState for each supported Orientation/ViewState. The VisualState name should match exactly the name of the Orientation/ViewState. For example i would name “Filled” the VisualState that is meant to design my UI when in Filled state.

Setting the attached property :
In this case the UserControl set’s it for demo purposes .It’s overridable.

Defining the VisualStates (For Win8) :

Quick tip : VisualStateGroups and VisualStateManager , i found, work *only* when defined/attached on the
RootElement of your Page or UserControl. Which means it wont work if you declare a control inside a Page, declare VisualStates on it and try to call VSM on that specific control.

Here’s the code for the OrientationHelper.

using System;
using System.Collections.Generic;
using System.Linq;

#if WINDOWS_PHONE
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using Orientation = Microsoft.Phone.Controls.PageOrientation;
#else
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Orientation = Windows.UI.ViewManagement.ApplicationViewState;
#endif

namespace OrientationHelper
{
    public static class OrientationHelper
    {
#if WINDOWS_PHONE
        private const String VisualStateGroupName = "PageOrientationStates";
#else
        private const String VisualStateGroupName = "ApplicationViewStates";
#endif

        #region IsActive DP

        public static readonly DependencyProperty IsActiveProperty =
            DependencyProperty.RegisterAttached("IsActive", typeof (bool), typeof (OrientationHelper),
                                                new PropertyMetadata(default(bool), OnIsActivePropertyChanged));

        public static void SetIsActive(UIElement element, bool value)
        {
            element.SetValue(IsActiveProperty, value);
        }

        public static bool GetIsActive(UIElement element)
        {
            return (bool) element.GetValue(IsActiveProperty);
        }

        private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as Control;
            if (control == null)
                return;

            SetupOrientationAwareControl(control, (bool)e.NewValue);

#if WINDOWS_PHONE
            //The control itself can be a page.In every case we need to retry until the containing page is loaded.
            control.Loaded += OnControlLoaded;
#endif

        }

        #endregion

#if WINDOWS_PHONE
        //Note: On the phone side Orientation is Page-specific so we keep the orientation aware controls
        //on the page itself using a Dependency Property
        #region OrientationAwareControls Private DP

        private static readonly DependencyProperty OrientationAwareControlsProperty =
            DependencyProperty.RegisterAttached("OrientationAwareControls", typeof (IList),
                                                typeof (OrientationHelper), null);

        private static void SetOrientationAwareControls(DependencyObject element, IList value)
        {
            element.SetValue(OrientationAwareControlsProperty, value);
        }

        private static IList GetOrientationAwareControls(DependencyObject element)
        {
            return (IList) element.GetValue(OrientationAwareControlsProperty);
        }

        #endregion
#else
        //Note: On the Windows 8 side Orientation is global
        private static IList<WeakReference> orientationAwareControls;
#endif

        private static void SetupOrientationAwareControl(Control control, bool isActive)
        {
#if WINDOWS_PHONE
            var page = FindParentPage(control);
            if (page == null)
                return;
            if (isActive)
            {
                //Add the orientation aware control to the list stored on the Page
                if (GetOrientationAwareControls(page) == null)
                {
                    SetOrientationAwareControls(page, new List());
                    //Start listening for changes
                    page.OrientationChanged += PageOnOrientationChanged;
                }
                if (!GetOrientationAwareControls(page).Contains(control))
                    GetOrientationAwareControls(page).Add(control);
                UpdateOrientationAwareControls(new[] { control }, page.Orientation); //Update this control only

            }
            else
            {
                var orientationAwareControls = GetOrientationAwareControls(page);
                if (orientationAwareControls != null)
                {
                    if (orientationAwareControls.Contains(control))
                    {
                        orientationAwareControls.Remove(control);
                        control.Loaded -= OnControlLoaded;
                    }
                    //Do cleanup for this page if there are no more controls
                    if (!orientationAwareControls.Any())
                    {
                        SetOrientationAwareControls(page, null);
                        page.OrientationChanged -= PageOnOrientationChanged;
                    }
                }
            }
#else
            if (isActive)
            {
                if (orientationAwareControls == null)
                    orientationAwareControls = new List();

                if (!GetOrientationAwareControls().Contains(control))
                {
                    orientationAwareControls.Add(new WeakReference(control));
                    // Note: On the Windows 8 side, probably due to aggressive optimizations,
                    // if the control is not named then when changing orientations/view states any
                    // WeakReference to that control will show it as GCed!
                    // Disclaimer : I ONLY TESTED ON THE SIMULATOR!!
                    if (string.IsNullOrEmpty(control.Name))
                    {
                        control.Name = Guid.NewGuid().ToString();
                    }
                    control.Loaded += OnControlLoaded;

                    if (control.Parent != null)
                    {
                        UpdateOrientationAwareControls(new[] { control }, ApplicationView.Value);
                    }
                }
                if (GetOrientationAwareControls().Any())
                {
                    //TODO: Handle Window changes (Closed)
                    Window.Current.SizeChanged += OnWindowSizeChanged;
                }

            }
            else
            {
                if (GetOrientationAwareControls().Contains(control))
                {
                    var controlWeakRef = orientationAwareControls.Single(weak => control.Equals(weak.Target));
                    orientationAwareControls.Remove(controlWeakRef);
                    control.Loaded -= OnControlLoaded;
                }
                if (!GetOrientationAwareControls().Any())
                {
                    orientationAwareControls = null;
                    Window.Current.SizeChanged -= OnWindowSizeChanged;
                }
            }
#endif
        }

        private static void OnControlLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            var control = sender as Control;
#if WINDOWS_PHONE
            SetupOrientationAwareControl(control, GetIsActive(control));
#else
            UpdateOrientationAwareControls(new[] { control }, ApplicationView.Value);
#endif
        }

#if WINDOWS_PHONE

        private static PhoneApplicationPage FindParentPage(FrameworkElement el)
        {
            FrameworkElement parent = el;
            while(parent != null)
            {
                if (parent is PhoneApplicationPage)
                    return parent as PhoneApplicationPage;
                parent = parent.Parent as FrameworkElement;
            }
            return null;
        }

        private static void PageOnOrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            var page = sender as PhoneApplicationPage;
            UpdateOrientationAwareControls(GetOrientationAwareControls(page), page.Orientation); //Update all controls
        }
#else
        private static void OnWindowSizeChanged(object sender, WindowSizeChangedEventArgs windowSizeChangedEventArgs)
        {
            UpdateOrientationAwareControls(GetOrientationAwareControls(), ApplicationView.Value);
        }
        private static IEnumerable<Control> GetOrientationAwareControls()
        {
            if(orientationAwareControls == null || !orientationAwareControls.Any())
                return Enumerable.Empty();

            IList<Control> aliveControls = new List<Control>();
            foreach (var controlWeakRef in orientationAwareControls.ToList())
            {
                if (controlWeakRef.IsAlive)
                    aliveControls.Add(controlWeakRef.Target as Control);
                else
                    orientationAwareControls.Remove(controlWeakRef);
            }
            return aliveControls;
        }
#endif

        private static void UpdateOrientationAwareControls(IEnumerable controls, Orientation orientation)
        {
            if (controls == null || !controls.Any())
                return;

            foreach (var control in controls)
            {
                VisualStateManager.GoToState(control, GetVisualStateName(orientation), true);
            }
        }

        private static String GetVisualStateName(Orientation orientation)
        {
            return orientation.ToString();
        }
    }
}

Here’s the whole solution : OrientationHelper.zip

Feel free to comment if you found it useful or found any issue.

Au revoir

Advertisements

A .NET Dev’s baby steps trying to share native code between Win8 and WP8

This is a walkthrough of what i had to do to setup native code sharing between Windows 8 and Windows Phone 8 (using the leaked WP8 SDK). My knowledge of the native world is quite limited and there might be other better ways of doing it. Feel free to comment if you know more about the subject and help out this poor .NET dev 🙂

First we create our Native C++ WinRT Component :

Add New Windows Runtime Component Project

Then we create our Native C++ Phone Runtime Component :

Next we delete from both projects the autogenerated files. From the WinRT project delete the files named Class1.cpp and Class1.h and from the PhoneRT project delete the files named NativeSharedPhoneRT.cpp and NativeSharedPhoneRT.h.

What we need next is a simple class that we will want to share between the two projects. Lets call it MySharedClass. We will create it only on the WinRT side and we will link to it from the PhoneRT side.

I am expecting that i can use preprocessor flags in the same manner that we used to do between Silvelight/WPF and Windows Phone. So the next question is “how do i define my preprocessor directives in C++ projects”?
These simple steps worked for me :

  1. Open the properties of the NativeSharedWinRT project and go to “Configuration Properties -> C/C++ -> Command Line
  2. Enter “/D “_WinRT”” (without the outside quotes) in  “Additional Options” and click Apply.

Screenshot below :

(Note: the only reason i named the directive with an underscore is because the debug directive in one of the templates was named _DEBUG.Thought it might be a convention or something)
As jalfdjalf pointed out directives starting with underscores are reserved (makes sense) so its OK to name our own as we wish (i.e PhoneRT, WinRT).Btw i used PhoneRT instead of the more “official” WinPRT for the phone because its a lot more distinguishable from WinRT.

Do the same thing for the NativeSharedPhoneRT project but add this “/D “_PhoneRT””  instead in the Additonal Options.
Great. Now we have setup our preprocessor directives for both our shared projects. Time to create the shared class. Go to the NativeSharedWinRT project and add an empty MySharedClass.cpp file and an empty MySharedClass.h file. Then go to the NativeSharedPhoneRT project, click “Add existing item” and navigate inside the NativeSharedWinRT folder to locate the newly created files and select those. These files are now linked and the project structure should look like this :

(Note: I tried linking the pch.cpp and pch.h files as well but VS and the compiler didnt like that for
some reason so i kept them unlinked in order to move one)

Ok so far. Now lets add code to our shared class and see how we can start sharing.
Below is only a screenshot of what the code inside our files should be but you will be able to
download the source code and copy paste directly (see download link at end of post).

Notice how opening the header file from inside the WinRT project that the _WinRT directive is active but not the _PhoneRT one just as we wanted. SharedMethodAsync is a WinRT-Style asynchronous method exposed to both WinRT and PhoneRT.

I had planned to also show off how projections worked in the WinRT world but i couldn’t get it to work easily.The plan was to expose a WinRT type like IMap or IIterable and see how they show up projected in C#.

Here is our .cpp file (opened from inside the NativeSharedPhoneRT project) :

Now its time to create our C# clients (one for WinRT and one for PhoneRT) that will consume our shared native libraries.
First is the WinRT C# Client :

Once created click “Add Reference” on it and select the NativeSharedWinRT project.

Big pause here! Can you say “where’s my P/Invoke dude?”  🙂
Now that’s what i call seamless.
Small note.Never forget that the interop cost is ALWAYS there when going back and forth from native <-> managed.Keep in mind that usually there is an interop cost when going native <-> managed and
that applies to WinRT as well although Microsoft has done a lot of work to minimize it.

Then is our PhoneRT C# Client :

Once created click “Add Reference” on it and select the NativeSharedPhoneRT project.

This is how the project structure should look now :

Finally lets see what the code looks like from both client projects.
This is what we get from the Phone Client :

and here is what we get from the Win8 Client :

And that’s it! That was certainly fun 🙂

Source code for this post is here